Compare commits

...

485 Commits

Author SHA1 Message Date
dranik
163cb11319 add send/post test dart 2026-06-26 15:44:15 +03:00
dranik
71ca24d56b remove comment & format code 2026-06-26 11:38:44 +03:00
dranik
1d55372da4 fix main thread & rename files 2026-06-26 11:38:44 +03:00
dranik
f85b810efe formate code 2026-06-26 08:55:18 +03:00
dranik
70222943dd integrate project (6) 2026-06-25 18:36:51 +03:00
dranik
1fbd49f55b fix 1106 & add support add_subdirectory 2026-06-25 15:28:07 +03:00
dranik
ff8ebbfdc9 C-ABI, shared agw_capi (agw_* up), C-smoke & Dart-smoke 2026-06-25 12:55:40 +03:00
dranik
7736fa1946 async & cancel (4) 2026-06-25 09:42:13 +03:00
dranik
7e6e70d1d3 add failover (3) 2026-06-25 08:33:17 +03:00
dranik
c859128cb2 init project agw-sdk (1) & add public include (2) 2026-06-24 18:04:52 +03:00
yp
5d16645b84 fix: android icons (#2725)
* update icons android

* remove comment
2026-06-24 00:08:21 +08:00
Yaroslav Gurov
203a092dc9 fix: blobs for macos-ne and codesigning of qt blobs (#2754) 2026-06-24 00:07:42 +08:00
yp
d8b8590bc4 fix: XRay validation audit (#2749)
* flow default and config fixes

* host/SNI/path validation backend + flow-default flag

* input validation, numeric limits and live Save on settings pages

* MinMaxRowType clamping + DropDownType fit-content drawer
2026-06-24 00:07:26 +08:00
yp
9b8bfaa6f8 fix: regression testing vs 4.8.15.4 (#2730)
* fixed revoke

* fixed async update xray/mtproxy/telemt

* fixed connect premium config

* fixed autostart app hide

* fixed clear profile

* (6) fixed xtls-rprx-vision→empty

* (7) fixed appendClient abort & fix restore admin

* (8) fixed async|clientsUpdated

* fixed increment name server N

* remove comment & reset file

* chore: add tr to nextAvailableServerName

---------

Co-authored-by: vkamn <vk@amnezia.org>
2026-06-23 23:05:58 +08:00
Yaroslav Gurov
890103a16a fix: update amneziawg (#2743)
* chore(conan): update amneziawg

* fix(conan): use cmake 4.2+ to support MSVC26

* fix(ci/cd): use the latest cmake generator available on windows
2026-06-17 19:56:53 +07:00
yp
56ab82f87f fix: Use shared OpenSSL on Android (#2736) 2026-06-16 10:57:32 +07:00
lunardunno
3984acbb44 feat: updating install_docker.sh script (#2661)
* Updating install_docker.sh script

Implementing a Docker service status check.
The Docker reinstall step has been removed due to the implementation of Docker service checking.
Implementing locale checking and assignment.
Implementation of execution of some actions through commands with sudo, to reduce delays caused by differences in the values ​​of the PATH variable for the root user and the user included in the sudo group.
Implementation of a verification step for the install containerization app to avoid installing unsupported podman-docker applications.

* adding message handling to install controller

Adding handling for "Containerization app is not supported" and "Service status not active" messages to the controller.

* Error Codes added

Error Codes added for ServerContainerizationNotSupported & DockerServiceNotActive

* Adding extended descriptions of new errors

* fix last line in errorCodes.h

* fix last line in errorStrings.cpp

* Changing the names of errors

* various changes in the script

The messages output for processing by the server controller have been changed: "Container runtime is not supported" and "Container runtime service is not running."
The redundant check and output of the "Packet manager not found" message, as well as the interruption of script execution, have been eliminated, as this situation is handled by the server controller at an earlier stage (check_server_is_busy.sh) and only there.
Added installation of the whish package if it is missing from the OS, for subsequent re-execution of the install_docker.sh and check_server_is_busy.sh scripts.
Implemented an alternative method for detecting the package manager if the whish package is initially missing from the OS.
The algorithm for setting the $pm variable (package manager) has been changed.

* processed phrases have been changed

The phrases processed by the server controller have been changed.

* Attempting to use "command -v"

Switching to using "command -v" instead of "which".

* "which" as main, "command" as backup.

* "which" as main, "command" as backup for check user

* which  LOCK_CMD with sudo

Run the "which" with sudo to check the $LOCK_CMD variable in case the user's PATH variable has incorrect values ​​if the user is not root and is only a member of the sudo group.

* suppressing sudo password prompt

* suppressing sudo password prompt

* suppressing sudo password prompt install_docker.sh

* Changing the phrase for check stdout

"sudo:" with "not found" instead of "command not found"

* Changing phrases for check stdout check_user_in_sudo.sh‎

* sudo|docker and not found, in one line

* check only sudoers
2026-06-15 22:28:38 +07:00
yp
cc404378f9 fix: remove only amnezia- prefixed docker volumes (#2728) 2026-06-15 13:12:19 +07:00
yp
594635e5cf fix: script remove docker volume (#2686)
* move sudo docker volume rm -f

* fix: remove unnecessary function

---------

Co-authored-by: vkamn <vk@amnezia.org>
2026-06-04 22:58:39 +08:00
vkamn
f9b106cf5b fix: various fixes (#2693)
* fix: fixed country model update

* fix: fixed context menu crush on ios

* fix: fixed passphrase dialog freeze

* fix: fixed country switch

* fix: fixed start minimized

* fix: fixed black screen after remove container

* refactor: return cloak and ss only for view

* fix: fixed default server change after improt while connected

* fix: divider visibility

* fix: fixed revoke admin user

* fix: fixed language restore after backup

* fix: link hover for tor settings page

* fix: fixed openvpn connecntion status

* fix: fixed free color status

* fix: fixed client config update

* chore: bump version
2026-06-04 22:45:53 +08:00
yp
a9861d18b7 fix: wrong index on xray pages (#2669)
* test crash xray

* fixed save config xray

* reset file

* fixed text port & reset file

* fixed textFieldWithHeaderType.textField
2026-06-01 12:22:54 +08:00
lunardunno
c14138f031 fix: deleting volumes when cleaning the server (#2673)
* Deleting volumes when cleaning the server

* force the remove volumes
2026-06-01 11:54:34 +08:00
yyy-amnezia
60686fde24 fix: link OpenVPNAdapter statically (#2645)
* fix(ios): link OpenVPNAdapter statically

* chore(conan): simplify openvpnadapter merge process and make everything via XCrun

---------

Co-authored-by: Yaroslav Gurov <ygurov@proton.me>
2026-05-30 13:59:35 +08:00
Yaroslav Gurov
bd0747296e fix: networkextension proper framework linking (#2668) 2026-05-28 23:09:49 +08:00
Yaroslav Gurov
ba61019a50 fix: enable bundled openssl for every platform except NE-based ones (#2660)
* fix: enable bundled openssl for every platform except NE-based ones

* fix(conan): trigger CI/CD on crutial cmake changes

* fix: install dylibs/dlls from conan and use proper RPATH

* fix: adjust windows runtime deps
2026-05-28 19:17:27 +08:00
vkamn
113f967006 fix: various fixes (#2664)
* fix: fixed nextAvailableServerName

* fix: fixed password request for ssh key
2026-05-28 15:14:04 +08:00
yp
bcee58b08b feat: add captcha (#2508)
* test capcha

* add test AMNEZIA_GATEWAY_PLAINTEXT_MOCK

* ref

* remove first QNetworkReply::NoError

* fixed macros

* fixed http code

* add test server

* fix cmake

* add CAPTCHA refreshed

* fixed captcha

* update QML Captha

* fixed crash app & up vercion & fix qml captha

* ver 4.9.0.1

* remove m_gatewayCaptchaStickyBase & outEffectiveRequestBase

* reset code PR

* remove mock & temp var AMNEZIA_LOCAL_GATEWAY

* ref code & remove AMNEZIA_LOCAL_GATEWAY

* remove check httpStatusCode & error

* add 408 status code

* fix update captca

* remove fallback на transport

* chore: add loader after captcha solved

* chore: remove logs from api utils

* chore: minor fixes

---------

Co-authored-by: vkamn <vk@amnezia.org>
2026-05-28 13:51:26 +08:00
MrMirDan
52de1acebf refactoring: move tests to separate repo (#2550)
* fix: AUTOMOC and AUTOUIC added

* update: native exports test

* update: export test

* update: vless serialization/deserialization test added

* update: language model and controller test

* update: sites UiController and Model test

* update: sites test

* update: app ui model and controller test

* update: allowed dns ui model and controller test

* update: env vars and removed some lines

* update: news ui model and controller test (incomplete)

* update: api services ui model and controller test (incompleted)

* update: job for tests (Linux)

* update: job for tests (Windows)

* update: proper artifact names

* update: added envs

* update: added 'get sources' and changed steps order

* update: tests jobs remake

* update: 'get sources' step and windows shell

* update: using ctest

* search for exe files

* changed path to run tests

* update: 'Build' step

* update: changed path to deploy qt dependencies

* update: dependencies only for tests executables

* update: ctest dir

* update: include ctest

* update: set dir for tests exe

* update: qt path

* update: serialization test

* update: removed api tests from cmake

* update: changed tests dir

* added ctest to client cmake

* update: installing msvc and additional checks

* removed mcvs install

* update: path to ssh.dll

* fixed issue with ssh path

* update: removed unneccessary step and line

* update: linux job step 'Run tests'

* update: linux 'Install dependencies'

* update: modified qtest include

* update: changed QVERIFY to QVERIFY2

* update: some qverify2 messages

* update: linux additional dependencies

* update: offscreen for linux tests

* update: MacOS tests job

* update: Android tests job

* update: rewrited env's, qtest include and clear clients in some tests

* update: added local vars file for tests

* proper path for some vars

* some fixes due merge

* update: windows tests deploy

* python and conan installation to tests jobs

* chore: minor fixes after merge with dev

* chore: move selfhosted admin tests to separate folder

* refactor: some rename

* chore: fixes after merge

* refactor: moved tests to separate repo

* refactor: remove tests from core controller

* chore: add more protected getters to core controller

* chore: add more protected getters

* chore: remove ctest

* chore: return xray model default values

---------

Co-authored-by: vkamn <vk@amnezia.org>
2026-05-28 12:31:27 +08:00
yp
027a12a1df fix: extended VLESS configuration (#2643)
* fixed vless

* fixed default var

* fixed save button

* remove comment

* fix: fixed header link in xray settings page

---------

Co-authored-by: vkamn <vk@amnezia.org>
2026-05-28 12:21:46 +08:00
yp
0a659a2d74 fix: various fixes for MTProxy & Telemt (#2653)
* fix color & fix enabled

* fixed remove base secret

* fix mtproxy/telemt 'base secret'

* fixed button back

* fixed loader

* fixed reload loader

* fixed dd secret

* fixed qml

* fix: fixed header link in mtproxy/telemt page

---------

Co-authored-by: vkamn <vk@amnezia.org>
2026-05-28 11:46:26 +08:00
vkamn
6f119cd083 fix: various fixes (#2662)
* fix: fixed dns processing

* fix: fixed proceesed index/id selection

* refactor: stop using the server index as state

* fix: fixed autostart and start minimized

* fix: fixed typo

* fix: add socks5 extractConfigFromContainer

* fix: remove unused currentContainerUpdated

* fix: fixed clear cached profile order
2026-05-28 10:57:08 +08:00
Yaroslav Gurov
1753aed3fc fix: use shared openssl on Android (#2657)
* feat(conan): clone openssl and patch it for Android

* fix(conan): build shared libssl for Android
2026-05-26 21:59:47 +08:00
Yaroslav Gurov
c714d98bd1 chore: extend prebuilts support for MacOS NE by clang 21+ (#2633)
* chore: extend prebuilts support for MacOS NE by clang 21+

* chore: trigger prebuilts on workflow file changes
2026-05-25 22:05:56 +08:00
vkamn
4787f3915b chore: defines for update controller (#2634) 2026-05-21 20:40:04 +08:00
Yaroslav Gurov
7a383116b2 fix: force os.sdk to empty on Darwin (#2632) 2026-05-21 18:56:53 +08:00
Yaroslav Gurov
d3de5f0f48 fix(conan): openvpn support in MSVC+Ninja setup (#2616) 2026-05-21 18:09:52 +08:00
vkamn
8749d683e3 chore: minor fixes (#2630) 2026-05-21 11:38:41 +08:00
vkamn
9de9d082bc chore: bump version (#2629) 2026-05-21 10:41:25 +08:00
Yaroslav Gurov
a4233fef41 fix: add ssh init and finalize for statically-linked libssh (#2627) 2026-05-21 10:19:08 +08:00
Yaroslav Gurov
4890dd1d74 chore: compare changes against base branch of the PR (#2626) 2026-05-20 21:26:41 +08:00
Yaroslav Gurov
564630827e сhore: apple ci cd macos versions (#2625)
* chore(ci/cd): use macos-26 for xcode>26.4

* chore(ci/cd): bump conan version

* chore(conan): remove redundant VirtualBuildEnv
2026-05-20 21:19:56 +08:00
Yaroslav Gurov
fbe15d965b chore: bump apple hev-socks5-tunnel dep (#2624)
* chore(conan): bump hev-socks5-tunnel

* chore(conan): bump xcode-versions

* chore(conan): upload prebuilts only in case of pushing to dev
2026-05-20 20:31:35 +08:00
vkamn
b29515c380 chore: rename artifacts (#2622) 2026-05-20 19:07:30 +08:00
vkamn
0658a8f565 revert: regional country codes UI (#2567) (#2621) 2026-05-20 13:55:28 +08:00
vkamn
482ec04b4a chore: bump version (#2620)
* chore: bump version

* chore: bump android qt version
2026-05-20 12:37:38 +08:00
vkamn
d40d24fcf9 fix: fixed validateAndPrepareConfig for non admin configs (#2617)
* fix: fixed validateAndPrepareConfig for non admin configs

* fix: fix fetchGatewayUrl lambda context
2026-05-20 12:37:22 +08:00
yp
fb5666057b feat: add extended vless configuration (#2566)
* update UI XRay, add new page PageProtocolXrayTransportSettings.qml PageProtocolXrayXmuxSettings.qml PageProtocolXrayXPaddingSettings.qml

* add UI PageProtocolXrayConfigsSettings, PageProtocolXrayFlowSettings, PageProtocolXraySecuritySettings

* add Xray-specific keys

* add vars xray model

* add new qml padding, update model

* update model and export

* rename file & update name class & update list xray

* fixed ui

* add save file in temp

* remove debug macros

* fixed build windows

* fix path Windows

* remove save config

* fixed changes

* fixed conf

* fixed UI

* fixed size & button save

* fixed build iOS

* fix: fixed headers base control

---------

Co-authored-by: vkamn <vk@amnezia.org>
2026-05-18 22:35:01 +08:00
yp
a49892c7e7 feat: add telemt container (#2435)
* Feat: Add MtProxy (Telegram)

* add path files

* Feat: Add Telemt (MtProxy)

* fixed secret & enum

* remove old path

* refactor: move logic from ui to core

---------

Co-authored-by: vkamn <vk@amnezia.org>
2026-05-18 20:01:09 +08:00
yp
277b295fd8 feat: add mtproxy(#2370)
* Feat: Add MtProxy (Telegram)

* add path files

* refactor: move logic from ui to core

---------

Co-authored-by: vkamn <vk@amnezia.org>
2026-05-18 19:52:58 +08:00
lunardunno
8c33779fc3 chore: Install recommends for apt (#2596) 2026-05-18 13:56:57 +08:00
lunardunno
f0299ca9fe chore: authentication prompt in Ubuntu 26.04 (#2603)
Handling the password prompt in Ubuntu 26.04
2026-05-18 11:55:07 +08:00
MrMirDan
c7b1c2809f fix: app buttons clicked instead of buttons in context menu (#2200)
* fix: app buttons clicked instead of buttons in context menu

* update: using MouseArea instead of changing popupType

* fix(cursor): fixed cursor type at opened context menu

---------

Co-authored-by: Mitternacht822 <sb@amnezia.org>
2026-05-15 21:02:09 +08:00
MrMirDan
c9ed0baf3b fix: app freezes when revoke awg/wg client during active connection (#2211)
* block configs revoke during connection

* update: check that current config is active

* update: notification text
2026-05-15 21:01:39 +08:00
yp
2a3e3126ac feat: regional country codes (#2567)
Co-authored-by: vkamn <vk@amnezia.org>
2026-05-15 15:44:58 +08:00
MrMirDan
98771027b7 fix: vless switch between dividers (#2600) 2026-05-15 14:58:23 +08:00
MrMirDan
0433e03bdc fix: amnezia free card button hovers when card enabled (#2602) 2026-05-15 14:58:11 +08:00
yp
cb48667b91 fix: bug when saving after canceling the save action (#2568)
Co-authored-by: vkamn <vk@amnezia.org>
2026-05-15 14:57:44 +08:00
yp
d0a1af0381 refactor: deactivate api config before remove (#2569)
Co-authored-by: vkamn <vk@amnezia.org>
2026-05-15 14:56:09 +08:00
Yaroslav Gurov
fd0c773918 fix: change artifact names (#2589) 2026-05-15 12:36:38 +08:00
vkamn
06372c8fd7 refactor: remove serverConfig struct (#2595)
* refactor: remove serverConfig struct

* refactor: add warnings for api v1 configs

* refactor: moved the server type definition to a separate namespace

* refactor: simplified gateway stacks

* fix: fixed server description

* fix: fixed postAsync reply usage

* fix: fixed validateConfig call

* fix: fixed server name in notifications

* fix: fixed initPrepareConfigHandler for lagacy configs
2026-05-15 12:33:36 +08:00
Yaroslav Gurov
009ca981d5 feat: initial conan support and build process refactoring (#2260)
* feat: initial conan support

* feat: add awg-go and awg-apple recipes

* feat: macos full feature conan build, except ss and cloak

* feat: conan android initial support

* fix: android libssh fixes

* conan: android additional recipes and fixes

* feat: openvpn add support android

* fix: awg android connection establish

* conan: apple full-featured support

* chore: bump min macos version

* chore: get rid of manual deploy recursive copying

* conan: beautify makefile-based recipes

* conan: add geosite.dat and geoip.dat

* conan: use lib linking instead of QT_EXTRA_LIBS for OVPN

* conan: address lack of SONAME of libck-ovpn-plugin.so correctly

* conan: windows initial support

* conan: make awg-windows and wintun be interpret as exes

* conan: fix version for v2ray-rules-dat

* feat: conan and platform bootstrap rework in cmake

* feat: 16kb support for Android

* chore(conan): recipes cleanup

* feat: support of drivers for windows

* feat: support full-featured cmake install

* chore: exclude qtkeychain from the target build

* fix: install for apple systems

* fix: provide flags for cloak plugin for openvpn-pt-android

* chore: bump android deps for 16kb support

* feat(conan): patch cloak to properly provide env for golang

* chore: remove redundant hint from conan find

* feat: linux <-> conan features

* feat: linux initial packaging support

* feat: linux cpack support

* feat: cpack windows full-featured build

* feat: productbuild cpack support

* feat: rework CI/CD for macos

* feat: rework CI/CD for Linux

* fix: libncap automake args

* fix: CI/CD correct QT paths

* fix: windows rework CI/CD

* fix: windows artifact upload

* chore: remove MacOS-old from build targets

* feat: add conan to all mobile and NE builds

* feat: support default amnezia conan remote

* fix: use Release instead of release on Android

* feat: get rid of 3rd-prebuilt

* feat: conan CI/CD upload

* fix: CI/CD change windows toolset versions

* fix: remove MSVC version from CI/CD

* feat: conan CI/CD add Release and Debug build types

* feat: add multiple xcode versions for conan CI/CD

* fix: correct conan CI/CD clang versions

* feat: separate prebuilt baking, and add some for NE

* feat: rework keychain on ios/macos even more

* fix: add desktop Qt for iOS

* feat: add QT_HOST_PATH to build.sh

* fix: add deploy definition to cmake

* fix: android adjustments for toolchains and CI/CD

* fix: add needs for Android CI/CD

* fix: Android CI/CD use android-28

* fix: modernize translations, and CI/CD fixes

* fix: gradle min sdk compilation error

* fix: CI/CD add installers to all jobs

* fix: parse android platform more precisely

* fix: adjust aab path in CI/CD

* feat: CI/CD do not execute artifact build if there is nothing changed

* fix: CI/CD use common jobs even if previous were failed

* fix: Apple CI/CD use set-key-partition-list for keychains

* fix: Apple CI/CD do not specify any keychain (use default)

* fix: build aab as a different step in build script

* chore: beautify build.sh script

* feat: CI/CD build separate APKs per ABI

* fix: Android CI/CD upload artifact in separate steps

* chore: recipes cleanup

* feat: add hints for conan on MacOS

* fix: add main.cpp and tests back to CMakeLists.txt

* chore: xrayProtocol codestyle changes

* fix: openssl set proper X509 request version

* fix: make openvpn protocol rely only on client while configuring

* chore: get rid of old scripts

* chore: readme update describing build process more precisely

* feat: windows build script add multiprocessing capabilities

* chore: bump Qt version in README

* feat: add generator option and use Ninja by default in CI/CD for linux/macos

---------

Co-authored-by: NickVs2015 <nv@amnezia.org>
2026-05-04 22:59:24 +08:00
cd-amn
c0cae0ff01 fix: outbound freedom for xray (#2479)
* fix: outbound freedom for xray on linux

* fix: outbound freedom for xray on macOS

* build: auto-generate pf rules based on the build type
2026-05-04 19:39:07 +08:00
Nethius
c28452a5da feat: desktop updater (#825)
* added changelog drawer

* Created a scaffold for Linux installation

* implement Linux updating

* Add debug logs about installer in service

* Add client side of installation logic for Windows and MacOS

* Add service side of installation logic for Windows

* ru readme

* Update README_RU.md

* Add files via upload

* chore: added clang-format config files (#1293)

* Update README_RU.md

* Update README.md

* feature: added subscription expiration date for premium v2 (#1261)

* feature: added subscription expiration date for premium v2

* feature: added a check for the presence of the “services” field in the response body of the getServicesList() function

* feature: added prohibition to change location when connection is active

* bugfix: renamed public_key->end_date to public_key->expires_at according to the changes on the backend

* feature/xray user management (#972)

* feature: implement client management functionality for Xray

---------

Co-authored-by: aiamnezia <ai@amnezia.org>
Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com>

* Fix formatting

* Add some logs

* Add logs from installattion shell on Windows

* Fix installation for Windows and MacOS

* Optimized code

* Move installer running to client side for Ubuntu

* Move installer launch logic to client side for Windows

* Clean service code

* Add linux_install script to resources

* Add logs for UpdateController

* Add draft for MacOS installation

* Disable updates checking for Android and iOS

* chore: fixed macos update script

* chore: remove duplicate lines

* chore: post merge fixes

* chore: add missing ifdef

* decrease version for testing

* chore: added changelog text processing depend on OS

* add .vscode to .gitignore

* Change updater downloading method to retrieving link from the gateway

* add Release date file creation to s3 deploy script

* Add release date downloading from endpoint

* update check refactoring

* feat: switch macOS auto-update from DMG to ZIP+PKG installer

- Update macOS artifact URL from .dmg to .zip
- Rewrite mac_installer.sh to extract ZIP and install PKG via osascript
- Increase download timeout to 30s for larger ZIP files

* fix: fix Android build

* feat: Change get request for updater link to post

* refactor: preparing NewsModel for update notifications

- Changed `updateModel` to `setNewsList` for better semantic meaning.
- Delegate model container updating to private method updateModel
- Updated the logic for marking news as read to use item IDs instead of a boolean flag.

* feat: Move update notification in news list

- Updated `UpdateController` to handle empty release dates in header text.
- Added `getVersion` method to `UpdateController` for version retrieval.
- Enhanced `NewsModel` to support update notifications with new methods for marking updates as skipped and setting update notifications.
- Updated QML pages to display update information and provide actions for updates and skipping them.
- Introduced `isUpdate` property in `NewsItem` to differentiate between regular news and updates.

* feat: Implement rate limit workaround for gateway requests

- Added a delay before contacting the gateway in both `UpdateController` and `ApiNewsController` to prevent rate limit issues caused by simultaneous requests.

* refactor: Convert synchronous network requests to asynchronous in UpdateController

- Updated `UpdateController` to use asynchronous network requests for fetching gateway URL, version info, changelog, and release date.
- Introduced `doGetAsync` method to handle asynchronous GET requests with error handling.
- Removed synchronous methods to improve responsiveness and prevent blocking the UI during network operations.
- Added a mechanism to prevent multiple concurrent update checks.

* chore: Decrease AmneziaVPN version to 4.8.10.0 in CMakeLists.txt for testing

* refactor: Improve update check handling to avoid rate limit issues

- Updated `CoreController` to initiate update checks after news fetching is complete.
- Removed synchronous waiting in `ApiNewsController` to streamline the fetching process.

* fix: fixed typo in IsReadRole

* fix: fix updater filenames

* chore: move updateController to core

* refactor: update to mvvm

* chore: tiny fix

---------

Co-authored-by: aiamnezia <ai@amnezia.org>
Co-authored-by: aiamnezia <ai@amnezia.com>
Co-authored-by: Pokamest Nikak <pokamest@gmail.com>
Co-authored-by: KsZnak <ksu@amnezia.org>
Co-authored-by: Cyril Anisimov <cyan84@gmail.com>
Co-authored-by: vkamn <vk@amnezia.org>
2026-05-04 12:37:19 +08:00
vkamn
396ce23228 fix: fixed xray config parsing in xrayprotocol (#2557) 2026-05-02 12:25:07 +08:00
vkamn
b05ee0a654 chore: return missing code after merge with mmvm (#2553) 2026-05-01 20:50:24 +08:00
vkamn
fd5051262d fix: fixed typo (#2542) 2026-04-30 15:46:15 +08:00
vkamn
847bb6923b refactor: refactor the application to the mvvm architecture (#2009)
* refactor: move business logic from servers model

* refactor: move containersModel initialization

* refactor: added protocol ui controller and removed settings class from protocols model

* refactor: moved cli management to separate controller

* refactor: moved app split to separate controller

* refactor: moved site split to separate controller

* refactor: moved allowed dns to separate controller

* refactor: moved language logic to separate ui controller

* refactor: removed Settings from devices model

* refactor: moved configs and services api logit to separate core controller

* refactor: added a layer with a repository between the storage and controllers

* refactor: use child parent system instead of smart pointers for controllers and models initialization

* refactor: moved install functions from server controller to install controller

* refactor: install controller refactoring

* chore: renamed exportController to exportUiController

* refactor: separate export controller

* refactor: removed VpnConfigurationsController

* chore: renamed ServerController to SshSession

* refactor: replaced ServerController to SshSession

* chore: moved qml controllers to separate folder

* chore: include fixes

* chore: moved utils from core root to core/utils

* chore: include fixes

* chore: rename core/utils files to camelCase foramt

* chore: include fixes

* chore: moved some utils to api and selfhosted folders

* chore: include fixes

* chore: remove unused file

* chore: moved serialization folder to core/utils

* chore: include fixes

* chore: moved some files from client root to core/utils

* chore: include fixes

* chore: moved ui utils to ui/utils folder

* chore: include fixes

* chore: move utils from root to ui/utils

* chore: include fixes

* chore: moved configurators to core/configurators

* chore: include fixes

* refactor: moved iap logic from ui controller to core

* refactor: moved remaining core logic from ApiConfigsController to SubscriptionController

* chore: rename apiNewsController to apiNewsUiController

* refactor: moved core logic from news ui controller to core

* chore: renamed apiConfigsController to subscriptionUiController

* chore: include fixes

* refactor: merge ApiSettingsController with SubscriptionUiController

* chore: moved ui selfhosted controllers to separate folder

* chore: include fixes

* chore: rename connectionController to connectiomUiController

* refactor: moved core logic from connectionUiController

* chore: rename settingsController to settingsUiController

* refactor: move core logic from settingsUiController

* refactor: moved core controller signal/slot connections to separate class

* fix: newsController fixes after refactoring

* chore: rename model to camelCase

* chore: include fixes

* chore: remove unused code

* chore: move selfhosted core to separate folder

* chore: include fixes

* chore: rename importController to importUiController

* refactor: move core logic from importUiController

* chore: minor fixes

* chore: remove prem v1 migration

* refactor: remove openvpn over cloak and openvpn over shadowsocks

* refactor: removed protocolsForContainer function

* refactor: add core models

* refactor: replace json with c++ structs for server config

* refactor: move getDnsPair to ServerConfigUtils

* feat: add admin selfhosted config export test

* feat: add multi import test

* refactor: use coreController for tests

* feat: add few simple tests

* chore: qrepos in all core controllers

* feat: add test for settings

* refactor: remove repo dependency from configurators

* chore: moved protocols to core folder

* chore: include fixes

* refactor: moved containersDefs, defs, apiDefs, protocolsDefs to different places

* chore: include fixes

* chore: build fixes

* chore: build fixes

* refactor: remove q repo and interface repo

* feat: add test for ui servers model and controller

* chore: renamed to camelCase

* chore: include fixes

* refactor: moved core logic from sites ui controller

* fix: fixed api config processing

* fix: fixed processed server index processing

* refactor: protocol models now use c++ structs instead of json configs

* refactor: servers model now use c++ struct instead of json config

* fix: fixed default server index processing

* fix: fix logs init

* fix: fix secure settings load keys

* chore: build fixes

* fix: fixed clear settings

* fix: fixed restore backup

* fix: sshSession usage

* fix: fixed export functions signatures

* fix: return missing part from buildContainerWorker

* fix: fixed server description on page home

* refactor: add container config helpers functions

* refactor: c++ structs instead of json

* chore: add dns protocol config struct

* refactor: move config utils functions to config structs

* feat: add test for selfhosted server setup

* refactor: separate resources.qrc

* fix: fixed server rename

* chore: return nameOverriddenByUser

* fix: build fixes

* fix: fixed models init

* refactor: cleanup models usage

* fix: fixed models init

* chore: cleanup connections and functions signatures

* chore: cleanup updateModel calls

* feat: added cache to servers repo

* chore: cleanup unused functions

* chore: ssxray processing

* chore: remove transportProtoWithDefault and portWithDefault functions

* chore: removed proto types any and l2tp

* refactor: moved some constants

* fix: fixed native configs export

* refactor: remove json from processConfigWith functions

* fix: fixed processed server index usage

* fix: qml warning fixes

* chore: merge fixes

* chore: update tests

* fix: fixed xray config processing

* fix: fixed split tunneling processing

* chore: rename sites controllers and model

* chore: rename fixes

* chore: minor fixes

* chore: remove ability to load backup from "file with connection settings" button

* fix: fixed api device revoke

* fix: remove full model update when renaming a user

* fix: fixed premium/free server rename

* fix: fixed selfhosted new server install

* fix: fixed updateContainer function

* fix: fixed revoke for external premium configs

* feat: add native configs qr processing

* chore: codestyle fixes

* fix: fixed admin config create

* chore: again remove ability to load backup from "file with connection settings" button

* chore: minor fixes

* fix: fixed variables initialization

* fix: fixed qml imports

* fix: minor fixes

* fix: fix vpnConnection function calls

* feat: add buckup error handling

* fix: fixed admin config revok

* fix: fixed selfhosted awg installation

* fix: ad visability

* feat: add empty check for primary dns

* chore: minor fixes
2026-04-30 14:53:03 +08:00
vkamn
2edd7de413 chore: minor fixes (#2524)
* fix: fixed i5 empty check

* fix: add check config format in extractConfigFromQr
2026-04-27 13:18:50 +08:00
vkamn
f0da2b003f feat: add fallback proxy endpoint (#2518) 2026-04-23 21:30:18 +08:00
vkamn
650c1c6ebb chore: bump version (#2502) 2026-04-20 20:32:59 +08:00
vkamn
8dbded1624 chore: remove ip from tunnel name for ios (#2489) 2026-04-17 15:02:54 +08:00
vkamn
cebfcc846e feat: add renewal for external-premium (#2485)
* feat: add renewal for external-premium

* chore: bump version

* chore: send subscription status for renewal link request
2026-04-17 15:01:24 +08:00
vkamn
4c18ceaa50 chore: minor fixes (#2477) 2026-04-14 16:27:46 +08:00
NickVs2015
ebe3a5dac6 fix: add linux reconnection (#2415)
* fix: add linux reconnection

* fix: Dbus error, fix race conditional

* fix: improve reeconnection

* fix: add dns load/unload

* feat:  catch  state changed via  check gateway

* revert: restore linuxfirewall.cpp

* fix: restore reconnect time

* fix: add   NM_STATE_DISABLED and  check getGatewayAndIface more carefully

* fix: reconnect

* fix: revert wireguardutilslinux

* fix: revert
2026-04-14 11:10:41 +08:00
yp
92deee5f67 fix: tun2socks auth settings (#2456)
* add parser auth/pass & fix port

* fix generateRandomHex

* remove hardcore port ios

* add generated random port

* fix sin6_port

* fixed inbound

* add error message

* add std::runtime_error & fixed random generator

* remove loop

---------

Co-authored-by: Yaumenau Pavel <yaumenau.pavel@planetvpn.dev>
2026-04-13 20:06:08 +08:00
lunardunno
a75bd0cf5e fix: set a fixed 3proxy ver 0.9.5 (#2468) 2026-04-13 12:27:45 +08:00
vkamn
46f5b3894b chore: minor fixes (#2459)
* fix: fixed links on page with service description

* fix: fixed subscription text color

* chore: update ru translations

* chore: add save button

* fix: ru translation fixes
2026-04-10 22:24:00 +08:00
Mitternacht822
493ee22883 chore: block vless toggle while active connection (#2318)
* fix: prevent disabled SwitcherType from toggling via keyboard

* fix: disabled vless option toggle while connection is active
2026-04-08 12:45:51 +08:00
yyy-amnezia
ad14847eb5 fix: ios ovpn fix (#2360)
* feat: enhance OpenVPN support and configuration handling for iOS and macOS platforms

* Deps updated

* Deps updated

* feat: add OpenVPN configuration validation and regeneration logic to VpnConfigurationsController

* revert: restore pre-fix OpenVPN NE flow

* chore: add OpenVPN NE payload diagnostics

* Revert "revert: restore pre-fix OpenVPN NE flow"

This reverts commit ae99cc77e9.

* chore: remove openvpn config processing

---------

Co-authored-by: vkamn <vk@amnezia.org>
2026-04-08 12:37:52 +08:00
lunardunno
cd50e0b8a5 fix: full server cleanup (#2446)
* Fix: full server cleanup

* Cleaning by REPOSITORY:TAG
2026-04-08 12:27:06 +08:00
vkamn
78f504e35c feat: new services description (#2412)
* feat: iap for apple now use storekit2

* fix: fixed error 101 on connection event

* feat: enhance StoreKit2Helper to handle entitlements and improve restore service from App Store functionality

* chore: add isInAppPurchase and isTestPurchase in primary config

* refactor: use end_date from primary config for renew ui

* fix: hide renew button for free

* fix: hide renew button for appstore purchases

* feat: add new premium info page

* feat: add new free info page

* chore: minor fixes

* refactor: move plan and benefits into separate models

* fix: fixed expired status when configs without an end date

* feat: add trial api support

* chore: add api message parsing for 422 error

* feat: move privacy policy and term of use to gateway

* feat: add iap support for new premium info page

* chore: minor fixes

* chore: minor fix

* chore: minor fixes

* feat: additional parsing for storekit subscription plans

* chore: minor codestyle fixes

* chore: simplify benefits

* chore: hide extend buttons on external premium

* feat: add trial error processing

* fix: remove wrong check from tiral handler

* chore: cleanup

---------

Co-authored-by: spectrum <yyy@amnezia.org>
2026-04-08 12:21:12 +08:00
NickVs2015
bf3d11e5c4 feat: renewal new status logic (#2409)
* fix: renewal add status logic

* fix: wakeup activity resumed android
2026-03-25 19:48:32 +08:00
NickVs2015
9a0222aee3 fix: ui fixes for renewal subscription (#2406) 2026-03-25 12:34:42 +08:00
NickVs2015
f0f0f7c5be feat: add subscription renewal (#2389)
* feat: add renewal subsribe

* fix: after review
2026-03-24 22:45:02 +08:00
NickVs2015
36b1a863bf fix: black screen resume / pause (#2400) 2026-03-24 22:13:31 +08:00
yyy-amnezia
4103c5bbcf refactor: extract and simplify OpenVPN reachability and network change handling logic (#2402) 2026-03-24 22:12:59 +08:00
vkamn
fa69da6d56 chore: send app version in services request (#2403) 2026-03-24 20:25:04 +08:00
yyy-amnezia
aaf2c9ddeb feat: add Xray split tunnel support for iOS PacketTunnelProvider (#2332) 2026-03-24 16:07:36 +08:00
Mitternacht822
dbbc7119ec feat: add warning info for ssh keys (#2252)
* fix: fixed da typo

* feat: added warning about available ssh keys info
2026-03-24 16:06:40 +08:00
vkamn
c57162c4cc feat: add base amnezia trial support (#2366)
* feat: add base amnezia trial support

* feat: add external-trial
2026-03-24 10:29:51 +08:00
NickVs2015
40e39895c9 fix openfile deadlock (#2373) 2026-03-21 11:46:46 +08:00
vkamn
ec3ab2a03c chore: update licnese file (#2376) 2026-03-20 21:04:13 +08:00
yyy-amnezia
ddecfcad26 fix: apple platform network switch fix (#2359)
* Apple platform network switch fix

* macos_ne exclusion fixed
2026-03-20 20:51:36 +08:00
NickVs2015
67bd880cdf fix: swap buffers error (#2347) 2026-03-16 13:03:20 +08:00
vkamn
477afb9d85 chore: bump version (#2336) 2026-03-10 22:22:37 +08:00
NickVs2015
f969fcdbb8 fix: restore dpad functionality ATV (#2335) 2026-03-10 22:19:55 +08:00
vkamn
b0ca16d861 chore: bump version (#2331) 2026-03-09 18:29:56 +08:00
NickVs2015
9963359948 fix: disable gamepad for GP (#2321) 2026-03-09 17:39:50 +08:00
vkamn
ca639d293d chore: bump version (#2319) 2026-03-06 23:11:03 +08:00
NickVs2015
83d045af64 fix: GP requrements (#2312) 2026-03-06 17:05:16 +08:00
NickVs2015
aea8ff4961 fix: add handle handleContextCreationFailure (#2309) 2026-03-03 22:04:45 +08:00
vkamn
1892db4375 fix: remove nested qeventloop from isConfigValid (also rename to validateConfig) (#2305)
* fix: remove nested qeventloop from isConfigValid (also rename to validateConfig)

* chore: bump version
2026-03-03 20:58:32 +08:00
NickVs2015
c86a641e05 fix: add suppord android 9 gamepad and remote control (#2302) 2026-03-03 15:14:51 +08:00
vkamn
befb2bf19a chore: bump version (#2295) 2026-02-27 23:33:37 +08:00
vkamn
7ad6bc340c chore: add translations for ru (#2285)
* chore: add translations for ru

* chore: text fixes
2026-02-27 20:00:31 +08:00
vkamn
9164e38c34 fix: restore backup android (#2291)
* fix: fixed restore backup on android

* chore: add resume helper for android

* chore: add ResumeHelper.runWhenActive call after all native android dialogs

* fix: add permission for tv file picker

* fix: add file picker handler in kotlin

---------

Co-authored-by: NickVs2015 <nv@amnezia.org>
2026-02-27 18:43:36 +08:00
vkamn
8f7559f01b chore: revert PR #2222 (#2290) 2026-02-27 14:29:25 +08:00
vkamn
af56200735 fix: fixed adding s3 s4 when updating the server conf for awg lagacy (#2289) 2026-02-27 14:11:40 +08:00
vkamn
3874050fae fix: again fixed s3, s4 ranges (#2288) 2026-02-27 13:37:49 +08:00
vkamn
3087163e34 fix: fixed s3, s4 ranges (#2283) 2026-02-26 22:31:41 +08:00
Mitternacht822
1fa152845c fix: generate native awg config as qr series (#2221) 2026-02-26 22:31:18 +08:00
vkamn
50e23ef233 fix: awg config update (#2281)
* fix: fixed client config update for awg container

* chore: bump version
2026-02-26 22:12:58 +08:00
Yaroslav Gurov
ea648466de chore: remove redundant VpnConnection usage from SitesController (#2278) 2026-02-26 17:55:08 +08:00
Yaroslav Gurov
b782775016 fix: change event looping to mutexes for settings and secureqsettings (#2270) 2026-02-26 11:41:08 +08:00
NickVs2015
89a7fe1081 fix: fixed remote control for ATV (#2277) 2026-02-26 11:40:16 +08:00
Yaroslav Gurov
e8bb096025 fix: ios wrong awg blob (#2272) 2026-02-24 17:56:17 +07:00
Mitternacht822
fd5c7c8322 fix: copy LICENSE to build as LICENSE.txt for WiX CPack (#2265)
* fix(installer): copy LICENSE to build as LICENSE.txt for WiX CPack

* fix: fixed a typo

* fix: fixed a typo
2026-02-24 14:07:48 +08:00
Yaroslav Gurov
e798d0f503 feat: update amneziawg-android dependencies (#2269) 2026-02-24 00:54:55 +08:00
Yaroslav Gurov
bbb0abb596 feat: update xray (#2267) 2026-02-24 00:27:29 +08:00
vkamn
0925aec86a chore: bump version (#2264) 2026-02-23 18:01:59 +08:00
Yaroslav Gurov
b084c4c284 fix: ios connection status stuck (#2263) 2026-02-23 18:00:13 +08:00
vkamn
87288ebccd chore: bump version (#2262) 2026-02-23 17:16:24 +08:00
vkamn
fcd7eadf4c chore: bump version (#2261) 2026-02-23 15:38:27 +08:00
Mitternacht822
0373338fb7 fix: randomized baseUrls traversal order in GatewayController::getProxyUrls (#2247) 2026-02-23 15:33:35 +08:00
Yaroslav Gurov
42f070fe9d fix: handle Android disconnected status properly (#2255) 2026-02-23 15:31:15 +08:00
Mitternacht822
02be6dc5f9 chore: add license to msi installer (#2227) 2026-02-20 12:13:08 +08:00
vkamn
bfcf7f0305 chore: bump version (#2244) 2026-02-19 20:27:42 +08:00
Mitternacht822
2bce595ade fix: remove revoke from remove subscription flow (#2226)
* fix(revoke): now revoke calls only for unlink device action

* fix: removed revoke call when removing a subscription from the app
2026-02-19 20:23:13 +08:00
Yaroslav Gurov
cd1e561fd4 fix: add network watcher back (#2240)
* feat: add reconnect in case of changing network

* fix: reconnect to VPN on wakeup

* fix: linux wakeup build
2026-02-19 20:21:49 +08:00
Mitternacht822
9bd1e6a0f5 fix: added stop and delete AmneziaVPNSplitTunnel on uninstall (#2222) 2026-02-18 11:21:59 +08:00
Yaroslav Gurov
5058c9aa6f fix: do not enable killswitch by default when service starts (#2232) 2026-02-18 10:59:16 +08:00
vkamn
d78416835c chore: change default i1 value (#2216) 2026-02-13 17:10:10 +08:00
vkamn
40e6c6aae3 feat: native wg with obfuscation (#2209)
* chore: change default i1 value

* feat: add i1 to native wg with obfuscation
2026-02-12 11:34:52 +08:00
Yaroslav Gurov
911a999c64 fix: xray stability and split-tunneling (#2187)
* fix: xray heap corruption

* fix: use proper configuration for split-tunneled apps

* chore: enable killswitch

* chore: xray windows split-tunneling cleanup

* chore: proper xray killswitch log

* feat: add wait for the tun device

* chore: update amnezia_xray deps for macos

* fix: add nullptr check for split-tunnel on win

* fix: modernize vpnAdapter grabbing function

* fix: remove network watcher due to its fragileness

* chore: xrayprotocol cleanup

* fix: correct wrong iface index on win

* chore: move tun2socks implementation to the client from the service

* chore: xrayprotocol cleanup

* chore: more xrayprotocol cleanup

* fix: consistent tun device with GUID specified

* chore: tun2socks logs

* chore: PrivilegedProcess cleanup
* better error handling in establishment phase
* terminate&kill ops for remote process

* fix: straighforward killing the process on windows

* fix: finally remove GUID setting from tun2socks due to instability

* fix: add sanitizer to ipc process

* chore: do not collect sensitive info from tun2socks
2026-02-11 23:47:28 +08:00
MrMirDan
b4f4184aa6 fix: returned mentioned lines (#2205) 2026-02-11 23:44:11 +08:00
NickVs2015
5c6db4b7a4 fix OpenGl error (#2185) 2026-02-10 11:15:31 +08:00
vkamn
f6277cdbb2 fix: native wg obfuscation (#2199)
* chore: bump version

* fix: fixed native wg obfuscation
2026-02-09 10:54:30 +08:00
NickVs2015
99312e61d3 fix: allow start Gamepad only Android (#2198) 2026-02-09 10:40:48 +08:00
NickVs2015
9f0ae75a2f feat: add gamepad buttons support android (#2066)
* feat: add support gamepad buttons

* feat: add support gamepad with github repo

* feat: add gitmodules dependency

* feat: add submodule qtgamepad

* chore: update qtgamepad submodule to commit 4e57142e563b931766056b4c7507c16892260222

* fix: update qtgamepad with standard CMake and private headers support

Update qtgamepad to commit f72b3e0 which:
- Replaces qt_add_library with standard add_library to avoid Qt 6.10 macro conflicts
- Copies private headers to build include tree for Android backend
- Creates Qt:: and Qt6:: namespace aliases for proper linking
2026-02-05 22:57:15 +08:00
vkamn
7960d8015d feat: add EULA and policy on IAP page (#2189) 2026-02-05 20:23:06 +08:00
vkamn
5dcc64e5e5 fix: deploy qopensslbackend on windows (#2190) 2026-02-05 20:22:47 +08:00
MrMirDan
964436ad43 fix: placeholder color, hide button image transparency, removed some lines (#2123)
* fix: placeholder color, hide button image transparency, removed unneccessary lines

* update: removed opacity on tunneling page

* update: remove opacity on app tunneling page
2026-02-05 12:56:41 +08:00
ik
4fc3900fd5 Merge pull request #2184 from amnezia-vpn/chore/add-release-date-upload
chore: add sending of release_date to s3
2026-02-04 12:20:23 +03:00
irvinklause
8f5e42dd61 chore: add sending of release_date to s3 2026-02-04 07:38:44 +00:00
Yaroslav Gurov
24895752c1 fix: added enablePeerTraffic call to xray (#2179)
* fix: add enablePeerTraffic call to xray

* chore: remove unnecessary steps during xray TUN setup phase

* chore: move tun init from tun2socks code to ipcserver

* chore: rework xray routing
* get rid of redundant delays
* check if remote calls are successful

* chore: xray routing fine-tuning

* fix: add service qt deps to deployment build
2026-02-04 12:35:53 +08:00
vkamn
87eccfb4ca fix: fix scrolling on drawers (#2183) 2026-02-04 12:35:17 +08:00
ik
a983d0504e fix: add checks for script components to find out where it can fall (#2169) 2026-01-30 14:43:30 +08:00
vkamn
d0b8535395 fix: update tag deploy (#2168) 2026-01-30 13:15:50 +08:00
dpamnezia
f84480cf56 chore: fix artifacts upload (#1961) 2026-01-30 12:43:21 +08:00
MrMirDan
de7a026ec1 fix: change drawer parents interactivity (#2004)
* fix: change drawer parents interactivity

* update: better vars names
2026-01-30 12:42:53 +08:00
MrMirDan
a128c7d247 fix: keyboard navigation (#2023)
* fix: self-hosted easy install card

* fix: label double click when enter/return pressed
2026-01-30 12:42:29 +08:00
MrMirDan
f316f0e25a feat: news notifications switch (#2126)
* feat: news notifications switch

* update: text changes

* fix: notifications enabled by default
2026-01-30 12:19:50 +08:00
NickVs2015
ea5242e29b fix: fixed cipher selection (#2110) 2026-01-30 12:18:54 +08:00
NickVs2015
b31a62c55f feat: add support open files by atv (#2082) 2026-01-30 12:11:26 +08:00
yyy-amnezia
02e3107a23 feat: implement service kickstart and improve macos post install script (#2131) 2026-01-30 12:05:20 +08:00
lunardunno
1862850108 feat: checking linux kernel version when installing amneziawg-go (#2098)
* Checking Linux kernel version when installing amneziawg-go

print the Linux kernel version to stdOut for subsequent checking by the server controller.

* Add error for old linux kernel

Add error 214 ServerLinuxKernelTooOld

* Add case for old linux kernel

Add case for error 214 ServerLinuxKernelTooOld

* Added kernel check for Awg2

Added Linux kernel version check and introduced corresponding ServerLinuxKernelTooOld error for Awg2.
2026-01-30 12:04:27 +08:00
vkamn
f73792844c chore: revoke #2148 (#2160) 2026-01-26 19:39:47 +08:00
Yaroslav Gurov
a7199ca6f5 fix: add +x permissions to wireguard-go on linux (#2159) 2026-01-26 19:16:39 +08:00
vkamn
5e757cdd3b chore: bump qt version for linux build (#2157) 2026-01-25 21:35:16 +08:00
vkamn
92af1f3268 chore: runners (#2150)
* chore: change runner for linux and android

* chore: add libsecret to linux build

* chore: bump version
2026-01-23 12:05:31 +08:00
Yaroslav Gurov
aad9d6dae2 chore: remove redundant gateway (#2148) 2026-01-22 18:21:15 +08:00
Yaroslav Gurov
423fe3fd4f fix: remove redundant gateway from xrayprotocol (#2147) 2026-01-22 18:03:36 +08:00
vkamn
b591dd7445 fix: minor fixes (#2137)
* refactor: removed premv1 migration code

* fix: i1-i5 parsing when scaning server

* chore: bump version
2026-01-19 14:03:54 +08:00
vkamn
a45bb5ea4f chore: bump version (#2108)
* chore: bump version

* chore: fix deploy.yml

* chore: return jurplel/install-qt-action@v3

* chore: bump qt version

* chore: disable cache

* chore: fix qt bin folder path

* chore: downgraded qt version for linux

* chore: disable gradle cache

* chore: use large runner for linux and android

* chore: change runner name for android and linux

* fix: change github runner label

* fix: set github runner specific os version in label

* chore: add self-hosted runner ubuntu-24.04-4cores

* fix: changed label to self-hosted for github runners

* fix: changed label to 4-core for github runners

* fix: fixed app closing delay

* fix: fixed awg description

* chore: bump version

---------

Co-authored-by: irvinklause <ik@amnezia.org>
2026-01-15 15:48:48 +08:00
yyy-amnezia
d859b111ca feat: awg connection states (#2091)
* Submodule amneziawg-apple updated

* feat: add support for controlled junk and special handshake timeout in AWG configurator

* refactor: improve AWG configurator and iOS controller logic

* awg_configurator.cpp reverted
2025-12-30 10:45:32 +08:00
Artyom Titov
52031efc48 fix(): set desktopFileName for Wayland (#2104) 2025-12-29 19:18:44 +08:00
vkamn
d78202c612 chore: is-test-flight processing (#2093)
* fix: context menu fixes for qt6.9

* chore: is-test-flight porcessing

* chore: bump version and minor build fixes

* refactor: moved test purchase processing on client side

* fix: fixed free import on ios

* chore: bump qt version in deploy.yml

* fix: minor fixes
2025-12-29 19:18:03 +08:00
yyy-amnezia
6bac948633 refactor: move iOS/macOS NE specific disconnect logic to the top of disconnectFromVpn method (#2100) 2025-12-27 11:09:11 +08:00
vkamn
a4c4ef71fb fix: minor fixes (#2099)
* fix: fixed saving i1-i5 fields

* fix: fixed default value for s4

* fix: fixed server name when sharing admin config
2025-12-26 22:55:57 +08:00
Yaroslav Gurov
127f85f4f0 fix: replace arm64 macos awg blob with amd64 one (#2096) 2025-12-24 22:28:31 +08:00
MrMirDan
13d4ddd292 chore: ru translation (#2086) 2025-12-23 20:17:27 +08:00
lunardunno
7265e09c85 chore: improved retrieving of images list (#2084)
Improved retrieving list of images named amnezia for Docker Engine 29.1.3 cleanup.
2025-12-23 12:20:44 +08:00
Yaroslav Gurov
2e629b6dac chore: bump awg version (#2088) 2025-12-19 23:40:48 +08:00
Yaroslav Gurov
92aba49705 fix: cannot connect to IPC on Windows (#2083)
* fix: replace localsocket by QtRO-embedded one

* fix: make IpcClient initialization lazy
2025-12-19 22:44:42 +08:00
vkamn
bec06b3a5e chore: bump version (#2080) 2025-12-19 11:46:10 +08:00
Yaroslav Gurov
91cd9474ea fix: safe IpcClient calls (#2076)
* fix: safe IpcClient calls

* fix: double free by specifying parent

* fix: windows includes for ikev2
2025-12-19 11:09:50 +08:00
Yaroslav
6178b05643 feat: ios in-app purchase methods (#1652)
* Add in-app purchase methods

* fix: init StoreKit controller on startup

* fix: Add transaction details to StoreKit callbacks

* nullpointer access fixed

* feat: in app purchase for ios

* feat: add IAP product fetching and logging for iOS platform

* feat: iOS Simulator building pipeline made

* feat: add support for multiple IAP product IDs and attempt purchase of the first valid one

* feat: add support for retrieving Base64-encoded app receipt after successful IAP purchase

* refactor: inapp-purchase code cleanup

* feat: iap processing

* refactor: move to storekit 2

* feat: add request to billing

* chore: add ios ifdef

* feat: remove iOS simulator specific code and exclusions

* refactor: remove unused StoreKit 2 transaction observer and simplify IAP product fetching logic

* feat: implement StoreKit 2 for iOS and macOS, add restore purchases functionality

* fix: Restore Purchases button appearance updated

* feat: enhance error handling and duplicate config detection in ApiConfigsController

* feat: add support for Mac OS NE in-app purchases and StoreKitController

* ci-cd fix

* Revert "ci-cd fix"

This reverts commit f22fd7a13b.

---------

Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com>
Co-authored-by: vkamn <vk@amnezia.org>
Co-authored-by: spectrum <yyy@amnezia.org>
2025-12-18 22:36:12 +08:00
vkamn
46ce22b85c fix: fixed awg2 container processing (#2067) 2025-12-18 22:25:20 +08:00
NickVs2015
36edafb985 feat: add qt 6.10.1 support (#2065)
* feat: switch to qt 6.10.1

* feat: switch to qt 6.10.1 remove touch
2025-12-18 20:18:32 +08:00
Yaroslav Gurov
d77eaba500 fix: make ipc client thread-safe (#2075) 2025-12-18 20:18:11 +08:00
yyy-amnezia
6a3d43fbb0 fix: iPad startup crash fix (#2071) 2025-12-17 21:54:27 +08:00
yyy-amnezia
4975955bbe feat: update GitHub workflow to use latest macOS, Xcode, and Qt versions, and add Go installation and gomobile setup (#2073) 2025-12-17 21:53:12 +08:00
Yaroslav Gurov
8f508783e3 fix: make ipc connection a singleton (#2069) 2025-12-16 23:05:31 +08:00
NickVs2015
f50817c43c feat: switch to qt 6.10.1 (#2057)
* feat: switch to qt 6.10.1

* feat: switch to qt 6.10.1 remove touch
2025-12-15 21:56:36 +08:00
Yaroslav Gurov
54f67b3d82 feat: native split-tunneling for xray (#1899)
* feat: integrated xray as a library and added split-tunneling

* fix: added copying amnezia_xray.dll to build dir

* fix: changed path on darwin

* chore: clean up getting default device

* chore: removed WSAGetLastError from sockopt logging

* fix: get rid of debug logs in xray handlers

* fix: minor fixes and xray debugging capabilities

* fix: macos default interface fix

* fix: roll-back ipv6 sockopt for mac

* fix: bind IPv6 on Windows

* fix: (win) better IPv6 handling and router fixes

* feat: prebuilts uploaded

* fix: removed redundant cmake definitions

* feat: moved xray to service process, reworked errors

* fix: return values in networkUtilities

* fix: macos build fixes

* fix: (windows) cmake fixes

* fix: (windows) compilation fix

* fix: (windows) changed location of amnezia_xray.dll

* feat: xray logs added to system service

* chore: bump xray&tun2socks versions for android

* chore: cleanup of XrayProtocol class
* removed killswitch
* removed redundant members and basic cleanup

* feat: support split-tunneling in iOS and macOS NE

* chore: update active interface index based on network path and available interfaces

* refactor: update network path handling and logging in PacketTunnelProvider

* chore: bump xray deps

---------

Co-authored-by: Yaroslav Yashin <yaroslav.yashin@gmail.com>
2025-12-15 21:54:34 +08:00
vkamn
d669adb707 feat: msi installer and cli command (#2020)
* feat: Add msi quite installer

* chore: update code for new wix

* feat: add cpack wix installer

* feat: add gihub workflow for msi

* chore: fix deploy script

* chore: add wix logs

* chore: fix msi build

* chore: fix msi build

* chore: add wix exts log

* chore: add cpackwixpatch for registering the service

* chore: fix build script

* chore: fix wix fragment

* feat: add closing app with reinstalling

* chore: update version for test

* chore: fix build script

* feat: added cli commands --connect and --import (#1967)

* fix: delete unused file and disable rollback after unsuccessful service start in msi installer

* fix: Add deps to msi

* fix: msi deps

* feat: added os signal handler

* fix: incorrect import at the empty client start (#2024)

* chore: add force quit for os signal handler

* feat: os signal handler improvements

* fix: fixed --connection command

---------

Co-authored-by: Mykola Baibuz <mykola.baibuz@gmail.com>
Co-authored-by: aiamnezia <ai@amnezia.org>
Co-authored-by: Mitternacht822 <sb@amnezia.org>
2025-12-11 18:54:24 +08:00
albexk
5103bc640e feat: implement reconnection in AWG by turning the VPN off and on (#2046) 2025-12-11 18:51:19 +08:00
vkamn
3e6f0c0342 feat: add timestamp to news list page (#2050) 2025-12-11 18:51:01 +08:00
vkamn
40950b92ee feat: awg 2 support (#1836)
* Add updated awg container

* add missing files

* Hide uninstalled AwgLegacy container

* Fix resources file

* Add role for allowed for installation containers

* Add native config sharing for new Awg container

* Fix not opening awg settings

* Remove AwgLegacy from wizard manual installation page

* Fix AmneziaWG settings

* chore: update link to submodule

* refactor: remove j1-j3 and itime

* chore: return s3 s4 fields to ui

* fix: awg2 native config compatability

* chore: update packet size validation

* feat: add awg2 support in self-hosted containers

* fix: delete parameters from server config

* feat: add H-parameters  validation as a strings

* chore: update link to submodule

* chore: add containers type for awg 1.5 and awg 2

* chore: fixed s3/s4 visibility for awg 1

---------

Co-authored-by: aiamnezia <ai@amnezia.org>
2025-12-11 15:18:36 +08:00
AnhTVc
ac77b4ee75 feat: add network status check for awg/wg protocol (#1894)
* Add network  status check for AWG/WG protocol

* Use service for PingSender

* Cleanup unused code

* Use networkchecker for all protocols

* fix android build

* add delay for ping checker stop

* handle for interafe problems on windows

* Restart IpcClient after OS suspend

* Add DBus network checker for Linux

* Use ping check for tun interfce

* Windows suspend mode handler

* MacOS suspend mode handler draft

* Add delay for Linux wakeup reconnect

* Add delay for Linux wakeup reconnect

* Fix macOS  wakeup/sleep prob

Fix macOS not receiving wakeup/sleep events

* fix done

* Update deploy.yml

fix CICD

* Update vpnconnection.cpp

update fix build CICD

* Update vpnconnection.cpp

update fix build cicd macos

* Update deploy.yml

fix  CICD build macos

* Update deploy.yml

fix CICD macos

* feat: implement SCP write buffer, improve network check and refactor macOS OpenGL support

* feat: add tunnel addresses updated signal and handle network check based on gateway and local address availability

* refactor: improve IpcClient connection handling and instance management

* fix: scp revert.

* fix: cmake reverted.

* fix: submodules updated

---------

Co-authored-by: Mykola Baibuz <mykola.baibuz@gmail.com>
Co-authored-by: Yaroslav Yashin <yaroslav.yashin@gmail.com>
Co-authored-by: vkamn <vk@amnezia.org>
2025-12-02 12:46:24 +08:00
NickVs2015
fbf652f818 feat: add vless string on sharing screen (#1999)
* feat: add vless config string and serialization

* feat: add vless config string and serialization
2025-12-02 12:09:04 +08:00
vkamn
bbbf4891e6 fix: fixed define name for linux os signal handler (#2030) 2025-12-02 11:14:09 +08:00
MrMirDan
20d005d66c fix: clear file name to remove header (#1984)
* fix: clear file name to remove header

* update: clear on signal

* removed uneccessary function

* fix: clear filename on invalid config type

---------

Co-authored-by: vkamn <vk@amnezia.org>
2025-12-02 11:13:26 +08:00
MrMirDan
c81ae2b060 fix: update or delete news on newsModel update (#2007)
* fix: update or delete news on newsModel update

* update: changed check for news editing

* update: changed news edit updating

* update: changed news model updating method

* chore: add rich text support for news page

---------

Co-authored-by: vkamn <vk@amnezia.org>
2025-12-01 20:23:14 +08:00
Yaroslav
105c42db1c fix: ipc call in macos ne (#1986) 2025-12-01 10:54:42 +08:00
Mykola Baibuz
89818ff63d fix: app freeze on quit (#1804)
* fix: app freeze on quit

* fix: typo in VpnConnection destructor

* add trace info

* add more trace info

* set timelimit for flushDns

* Refactor IpcClient::Interface access logic

* cleanup unused variable

* cleanup trace info

* fix: remove second disconnect from VPN on app close

* this object will be deleted at app close

* Don't terminate VPN thread on Linux

* Revert "Don't terminate VPN thread on Linux"

This reverts commit 20e4ea2d4a.

* disconnect all signals from vpnconnection on exit

* add interruption request on vpnConnectionThread

* use checktimer only for iOS

* disconnect all signals from vpnconnection on exit

* disconnect signals on exit before VPN disconnect

* add disconnectSlots method

* fix: add allow traffic rules on killswitch disable

* wait for response from service before object destroy

* change disconnect from vpn order

* add delay for connection close

* change disconnect method order

* use stop method for protocol disconnecect

* change disconnect method order

* allow dns traffic after app close

* delete tun on disconnect

---------

Co-authored-by: vkamn <vk@amnezia.org>
2025-12-01 10:49:16 +08:00
vkamn
414c422177 feat: added os signal handler (#2029) 2025-12-01 10:45:06 +08:00
NickVs2015
b39ac8556c feat: add right artifact name (#2018) 2025-11-28 12:08:38 +08:00
MrMirDan
5e1742262d fix: eye icon (#1985)
Co-authored-by: vkamn <vk@amnezia.org>
2025-11-28 11:00:53 +08:00
VoyNaLunu
5a07a1274f fix: GetBestRoute always returning 1231 error (#1981)
* fix GetBestRoute always returning 1231 error

* revert some changes because fix turned out to be simpler
2025-11-26 12:46:55 +08:00
MrMirDan
7b8ff1fd6e fix: checked format after changing protocol (#1937)
* fix: checked format after changing protocol

* update: improved some lines

* fix(ui): restore checkmark for connection format after switching protocol

* fix: correct a typo

* fix(ui): escape regex in client search filter

* refactor: removed redundant lines

---------

Co-authored-by: Mitternacht822 <sb@amnezia.org>
2025-11-26 12:07:24 +08:00
MrMirDan
c7221832e0 fix: users search field clears on 'x' button or 'escape' key clicked (#1920) 2025-11-26 11:57:28 +08:00
NickVs2015
eb7d031c7d fix: clear qt cache on start app (#2008)
* Fix/ Cache clear Android

* Fix: Clear cache on start app

* chore: bump version

---------

Co-authored-by: vkamn <vk@amnezia.org>
2025-11-26 11:47:50 +08:00
vkamn
3b3a0aaceb chore: bump version (#1997) 2025-11-18 00:22:58 +08:00
vkamn
01ec79b7d5 fix: news fetch (#1994)
* fix: fixed news nested qml call

* feat: async proxy bypass
2025-11-18 00:21:02 +08:00
vkamn
3d6339e2dd chore: bump version (#1989) 2025-11-14 13:59:47 +08:00
NickVs2015
b4d78d865a fix: fix android crash (#1988) 2025-11-14 13:57:52 +08:00
NickVs2015
b53cdcff08 fix: fix self-hosted TextFields and Keyboard reset issue (#1983)
Co-authored-by: vkamn <vk@amnezia.org>
2025-11-12 15:57:53 +08:00
vkamn
3cc18c5807 chore: bump version (#1982) 2025-11-11 23:03:24 +08:00
NickVs2015
5fdce1e49e fix: fix ui android issues (#1980)
* Fix UI issues

* Fix Screen Swipe
2025-11-11 22:03:27 +08:00
Yaroslav
2ee61a040b fix: iOS appstore publish fix (#1922) 2025-11-04 12:10:30 +08:00
vkamn
741b5cc0f9 fix: qt6 9 support (#1973)
* Fix qt 6.9 support

* add support android sdk 36

* feat: add support SafeMargins from Android

* Fix black screen

---------

Co-authored-by: NickVs2015 <nv@amnezia.org>
2025-11-04 11:43:36 +08:00
MrMirDan
aaf0e070dc fix: hide description (#1959) 2025-11-03 10:27:01 +08:00
vkamn
e0e126eda8 chore: bump version (#1969) 2025-11-03 10:26:33 +08:00
vkamn
236daf6b3b feat: ad label (#1966)
* refactor: ad label desing refatroing

* feat: add ad label settings processing

* chore: fix ru translations

* chore: minor fixes
2025-11-03 10:26:22 +08:00
vkamn
f1481b1b1f feat: add async post in gateway controller (#1963) 2025-10-29 23:24:24 +08:00
vkamn
f6e7d3ccf1 fix: minor ui fixes (#1917)
* feat: improve storage processing

* fix: minor ui fixes
2025-10-09 23:22:58 +08:00
Mitternacht822
a754a11913 fix: added displaying vpn_key field added in older version of the app (#1873)
* fix(api_key): added displaying vpn_key field added in older version of the app

* revert changes

* fix: implemented generation of api key text for PremiumV2

* fix: deleted unnecessary code

* saving apikey text when generating

* added method for vpn key export, fixed wrong saving file
2025-10-07 23:16:28 +08:00
vkamn
4d25e3b6f6 chore: minor bugfixes (#1915) 2025-10-07 23:15:06 +08:00
MrMirDan
1fac280497 fix: main app info added after clearing logs (#1913) 2025-10-06 21:07:04 +08:00
Yaroslav
c886c5e6a7 feat: enhance OpenVPN configuration handling and logging for iOS plat… (#1910)
* feat: enhance OpenVPN configuration handling and logging for iOS platform

* refactor: remove $OPENVPN_TA_KEY_SANITIZED and use $OPENVPN_TA_KEY instead
2025-10-06 21:04:49 +08:00
aiamnezia
cd7f78b9ca feat: news and notifications page (#1660)
* Add news and notifications

* Add localization for news and notifications

* Remove news caching

* Add fetching news befor openning news page

* Fix not updating news page

* Delete debug output

* Remove news and notificztions with only self-hosted servers

* Add stack filters to fetching news request

* Add fetching news with changing stack in the client

* small refactoring

* polishing

* Rename newsModel files and fix naming in code

* fix: remove custom signals; fetch news only on stack expansion

* chore: delete unnecessary code

* chore: code style fixes

* fix: fixed memory leak in gateway controller

---------

Co-authored-by: vkamn <vk@amnezia.org>
2025-10-06 12:06:36 +08:00
vkamn
a587d3230f fix: again fixed site link for features field (#1908) 2025-10-06 11:38:57 +08:00
MrMirDan
93e7b45136 fix: removed 'clear site list' button icon (#1909) 2025-10-06 11:37:42 +08:00
vkamn
e024f71ce1 fix: allow remove expired api configs (#1907) 2025-10-03 14:45:12 +08:00
MrMirDan
50d1be7b4a chore: update for RU translation (#1893) 2025-10-02 20:59:45 +08:00
MrMirDan
3ec6d8973b fix: warning visible only on windows (#1900) 2025-10-02 20:59:23 +08:00
Yaroslav Gurov
3ea47d31a9 fix: restore dns after using xray (#1902) 2025-10-02 20:58:53 +08:00
vkamn
30c8cc4548 feat: add isConnectEvent field to api request (#1896) 2025-09-30 12:10:27 +08:00
vkamn
98586d2dd9 fix: fixed site link (#1897) 2025-09-30 12:07:27 +08:00
vkamn
c66d8ecca0 chore: bump version (#1892) 2025-09-29 11:07:27 +08:00
vkamn
db535f7e7d chore: increase default values (#1891) 2025-09-29 11:05:30 +08:00
vkamn
89f30d8c31 fix: fixed native wg obfuscation (#1890) 2025-09-29 10:58:44 +08:00
Yaroslav
8bce432824 fix: enable paste from clipboard on ios in addition to android (#1868) 2025-09-29 10:56:41 +08:00
MrMirDan
f3539b2632 fix: proper wl name on connection key page (#1867)
* fix: proper wl name on connection key page

* some changings

* little change

* added missing import

* fix: proper wl default filename
2025-09-29 10:55:53 +08:00
MrMirDan
7a96c212f3 fix: rename user in search (#1847) 2025-09-29 10:51:52 +08:00
MrMirDan
2d5dc54e0f fix: keyboard navigation for text fields (#1879) 2025-09-29 10:50:57 +08:00
MrMirDan
cef4c262e9 fix: keyboard fix for api 'connection key' buttons (#1872) 2025-09-29 10:50:18 +08:00
MrMirDan
34309261a8 fix: scrollbar always visible (#1877)
* fix: scrollbar always visible

* fix: scrollbar always visible on app split tunneling page
2025-09-29 10:49:19 +08:00
MrMirDan
657eeb40c7 fix: mirror error code link (#1863)
* fix: mirror error code link

* remake
2025-09-29 10:48:36 +08:00
MrMirDan
b4938c2cc9 fix: default lang matching between app and OS (#1855)
* fix: default lang matching between app and OS

* remake

* fix: set default lang value
2025-09-29 10:47:54 +08:00
MrMirDan
524fefc5cb feat: warning on app split tunneling for windows (#1880) 2025-09-29 10:45:14 +08:00
Yaroslav
73f13404bb feat: add support for multiple scenes and handle URL contexts in iOS 13+ (#1889) 2025-09-29 10:40:58 +08:00
MrMirDan
5fc68cca83 fix: split tunneling restoration from backup (#1835) 2025-09-15 10:55:18 +08:00
Mitternacht822
fcb7b8fa8d fix: save/restore AmneziaDNS state (#1833) 2025-09-15 10:54:34 +08:00
aiamnezia
a81e32ff95 fix: clean service/client logs in uninstall scripts (#1846)
- Windows (x64/x86):
  - Remove delegation to `AmneziaVPN.exe -c`
  - Delete `%ProgramData%\AmneziaVPN\log\AmneziaVPN-service.log`
  - Delete current user logs at `%AppData%\AmneziaVPN.ORG\AmneziaVPN\log`
  - Remove empty parent dirs (app/org, log)

- Linux:
  - Delete only `/var/log/AmneziaVPN/AmneziaVPN-service.log` (preserve `post-uninstall.log`)
  - Delete current user logs at `$HOME/.local/share/AmneziaVPN.ORG/AmneziaVPN/log`
2025-09-15 10:53:51 +08:00
albexk
c897052107 chore: bump version (#1850) 2025-09-10 19:28:36 +08:00
vkamn
4d0efc7ea5 fix: remove duplicate m_vpnConnection delete from AmneziaApplication destructor (#1848) 2025-09-10 15:01:52 +08:00
Ivan
a77842c9e3 feat: add server diagnostics script (#1837)
Co-authored-by: Ivan Istomin <istomin-ms@yandex.ru>
2025-09-09 19:33:35 +08:00
Mitternacht822
0ded9db780 refactor: use QCommandLineOption members for autostart/cleanup (#1820)
* refactor(app options): use QCommandLineOption members for autostart/cleanup

* fix(app): initialize QCommandLineOption members in ctor/field to avoid no-default-ctor build failures
2025-09-03 12:03:45 +08:00
Mitternacht822
58d480fcb5 fix: moved startMinimized to Q_Property (#1819) 2025-09-03 12:03:10 +08:00
aiamnezia
7154428d26 fix: sharing QR code size (#1830) 2025-09-03 11:58:36 +08:00
MrMirDan
02a52d0169 fix: full config default filename (#1831) 2025-09-03 11:57:30 +08:00
MrMirDan
ec60764072 fix: rename/revoke user while in search on share page (#1787)
* fix: revoke user config

* fix: user renaming

* fix: revoke signal

* some fixes

* remaded fix
2025-09-03 11:56:08 +08:00
MrMirDan
17d2fa5532 fix: premium key duplication (#1818)
* ru translation fix

* crc saving

* little fix

* updated crc saving

* fix: added comparison by key

* remaded fix
2025-09-03 11:54:11 +08:00
MrMirDan
3ca8b534e8 fix: go to home page after first protocol manual installation (#1829) 2025-09-03 11:52:45 +08:00
MrMirDan
e88f7c5e46 fix: index assignment (#1821) 2025-09-02 13:03:05 +08:00
MrMirDan
3ac5d7bd1f chore: ru translation update (#1815) 2025-08-27 18:37:43 +08:00
vkamn
19cad00a00 fix: minor ui fixes (#1817)
* fix: minor ui fixes with services list

* fix: fix page share connection headers and config description
2025-08-27 16:42:28 +08:00
vkamn
1ea716a163 fix: fix page share connection headers and config description 2025-08-27 16:41:20 +08:00
vkamn
4551659c2a fix: minor ui fixes with services list 2025-08-27 15:15:53 +08:00
MrMirDan
c568bf8c24 chore: ru translation update (#1812)
* ru translation update

* fixes
2025-08-26 20:32:00 +08:00
vkamn
a412d91105 feat: subscription expiration processing (#1814) 2025-08-26 20:31:41 +08:00
vkamn
ad01f23bbe feat: add service description customization (#1811) 2025-08-26 12:17:37 +08:00
vkamn
656070b132 feat: add request id (#1809) 2025-08-25 22:05:00 +08:00
MrMirDan
c907f5ca36 fix: removed service logs section for mobile platforms (#1810) 2025-08-25 22:04:48 +08:00
Mykola Baibuz
94a13b2b54 fix: set guid for windows tun2socks tun interface (#1808) 2025-08-25 11:03:42 +08:00
MrMirDan
169f11d9c7 chore: added trimming I's and J's params on save (#1774)
* trimming params on save

* removed unused code
2025-08-21 12:29:22 +08:00
vkamn
816dc3af95 feat: add ping before request to proxy (#1805) 2025-08-21 12:28:03 +08:00
Mykola Baibuz
b802863de5 fix: check for empty secondary DNS (#1799) 2025-08-20 14:19:22 +08:00
vkamn
8dc2a4b76c fix: fixed switcher behavior (#1801) 2025-08-20 13:01:09 +08:00
vkamn
beb1c6dbf2 feat: added cache for proxy bypass (#1797) 2025-08-20 13:00:35 +08:00
vkamn
3eb06916c7 chore: bump version (#1802)
* chore: bump version

* fix: fixed ios build
2025-08-20 13:00:20 +08:00
Cyril Anisimov
30d0f84a4f fix: fixed focus view and reverse focus change in headers (#1791)
* fix: add view movement on changing the focus in backwards direction

* fix: return value in isFirstFocusItemInHeader function
2025-08-20 12:59:57 +08:00
Mykola Baibuz
251f2aa5db fix: remove double disconnect for Win IPSec (#1800) 2025-08-20 12:58:39 +08:00
Nethius
16d92ddb7c fix: UI fixes after merge with d20ed4a (#1779)
* fix: ui fixes after merge with d20ed4a

* update OpenVPN settings page

* chore: page settings dns margins

---------

Co-authored-by: Cyril Anisimov <CyAn84@gmail.com>
2025-08-11 13:40:28 +08:00
Cyril Anisimov
e9d4fd8482 fix checkbox switch (#1777) 2025-08-10 11:13:58 +08:00
Yaroslav
9fdcf5ab13 feat: macos with network extension Implementation (#1468)
* There's a common issue of building iOS apps on Qt 6.8 because of new introduced ffmpeg dependency in multimedia Qt package
ref: https://community.esri.com/t5/qt-maps-sdk-questions/build-failure-on-ios-with-qt-6-8/m-p/1548701#M5339

* Cmake related changes

* Source code changes

* Various entitlements

* Ci-cd config update

* Resources changes

* Submodules updated

* Remove me

* QtWidget exclusion omitted

* Distribution errors fixed

* Outdated files deleted

* macos_ne cmake fixed

* fix: update provisioning profile specifiers for macOS network extension

* fix: update provisioning profile specifiers and code sign flags for macOS build

* Revert me
(temporary 3rd-build commit pointer)

* fix: Welcome screen fix

* fix: ci/cd hanging forever fix

* fix: Fixed error popup on macos on file save

* refactor: rename networkextension target to AmneziaVPNNetworkExtension in macos build configuration

* feat: add autostart support for Mac App Store builds on macOS

Fixes: QA-8

* feat: add debug logging to Autostart functionality on macOS

* Revert "feat: add autostart support for Mac App Store builds on macOS"

This reverts commit 3bd25656fb.

* feat: add platform-specific close window behavior for macOS App Store build with Network Extension

Closes: QA-12

* When the application starts with "Start minimized" enabled on macOS (especially the
sandboxed App-Store build compiled with MACOS_NE), fully hiding the window prevents it
from being restored by clicking the Dock icon. The proper behaviour is to start the
window in the *minimized* state instead. That way the window is still part of the
window list and the system automatically brings it back when the user clicks the Dock
icon, replicating the native experience.

On the other platforms we keep the old behaviour (hide the window completely and rely
on the tray icon), therefore we switch at runtime by checking the current OS.

Closes: QA-7

Closes: QA-8

* Revert "When the application starts with "Start minimized" enabled on macOS (especially the"

This reverts commit 7b0d17987c.

* feat: MACOS_NE systray menu support

* feat: add macOS notification handler and install event filter on main window

* feat: implement custom close behavior for Amnezia application on different platforms

* fix: update provisioning profile specifiers for macos builds

* fix: Fatal error in logs

CLI-216

* fix: disabled unavailable on macos ne service logs

* fix: dock icon now hides only when window is closed; menubar icon shows always

Initial state of the docker icon to be presented follows "Start minimized" setting in app settings.

* temp-fix: temporary disable all OpenVPN options of VPN on MACOS_NE since it's not working yet.

* fix: build script updated

* feat: add macOS NE build workflow to GitHub Actions

* fix: Not working Auto start toggle is hidden

* fix: Log spamming during xray connection fixed

* 3rd-prebuild points to commit that stores macos_ne universal binaries.

* fix: missing native dependency on linking stage fixed

* chore: update link to submodule

---------

Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com>
2025-08-10 11:12:19 +08:00
serj95reg
a6e6de33c8 feat: updated xray version in dockerfile to 25.8.3 (#1771) 2025-08-08 10:34:51 +08:00
Mitternacht822
53c7fd4d81 fix: android build (#1768)
* added signal-slot connection between corecontroller and systemtraynofificationhandler updating websiteurl

* cleared up the commented lines

* fixed andorid includes for systemtraynotificationhandler
2025-08-07 11:12:09 +08:00
Nethius
2608ea4367 chore: fix typo (#1769) 2025-08-06 11:00:43 +08:00
Cyril Anisimov
d20ed4ad01 refactoring: improved stability of focus controller (#1464)
* change position view mode

* remove `parentFlickable` from `PageShare`

* replace `FlickableType` with `ListViewType` in `PageSettings`

* reorganize `PageSettingsAbout` for improved structure

* replace `Flickable` with `ListViewType` in drawer in `PageSettingsApiNativeConfigs`

* replace `FlickableType` with `ListViewType` in `PageSettingsApplication` and update layout structure

* replace `FlickableType` with `ListViewType` in `PageSettingsAppSplitTunneling` and adjust layout for better structure

* replace `FlickableType` with `ListViewType` in `PageSettingsBackup`

* replace `FlickableType` with `ListViewType` in `PageSettingsConnection`

* replace `FlickableType` with `ListViewType` in `PageSettingsDns`

* replace `FlickableType` with `ListViewType` in `PageSettingsLogging`

* replace `FlickableType` with `ListViewType` in `PageSettingsServerData`

* update structure of `PageSettingsServerProtocol`

* update `PageSettingsServersList`

* replace `ListView` with `ListViewType` in `PageSettingsSplitTunneling`

* replace `FlickableType` with `ListViewType` in `PageServiceDnsSettings`

* update `PageServiceSftpSettings`

* update `PageServiceSocksProxySettings`

* replace `FlickableType` with `ListViewType` in `PageServiceTorWebsiteSettings`

* replace `FlickableType` with `ListViewType` in `PageSetupWizardApiServiceInfo`

* update `PageSetupWizardApiServicesList`

* replace `ListView` with `ListViewType` in `PageSetupWizardConfigSource`

* replace `ListView` with `ListViewType` in `PageSetupWizardCredentials`

* replace `FlickableType` with `ListViewType` in `PageSetupWizardEasy`

* replace `FlickableType` with `ListViewType` in `PageSetupWizardInstalling`

* replace `ListView` with `ListViewType` in `PageSetupWizardProtocols`

* replace `FlickableType` with `ListViewType` in `PageSetupWizardProtocolSettings`

* replace `FlickableType` with `ListViewType` in `PageSetupWizardTextKey`

* replace `FlickableType` with `ListViewType` in `PageSetupWizardViewConfig`

* update `PageProtocolAwgClientSettings`

* update `PageProtocolAwgSettings`

* replace `FlickableType` with `ListViewType` in `PageProtocolCloakSettings`

* replace `FlickableType` with `ListViewType` in `PageProtocolRaw`

* replace `FlickableType` with `ListViewType` in `PageProtocolShadowSocksSettings`

* replace `FlickableType` with `ListViewType` in `PageProtocolWireGuardClientSettings`

* replace `FlickableType` with `ListViewType` in `PageProtocolWireGuardSettings`

* replace `FlickableType` with `ListViewType` in `PageProtocolXraySettings`

* replace `FlickableType` with `ListViewType` in `PageShareFullAccess`

* replace `FlickableType` with `ListViewType` in `PageDeinstalling`

* update `PageDevMenu`

* remove `Flickable` references in `LabelWithButtonType`

* remove useless key navigation handlers from `ListViewType`

* replace `ListView` with `ListViewType` in `ListViewWithRadioButtonType.qml` and remove unnecessary properties

* remove references to `Flickable` in `TextAreaType.qml`

* remove references to `Flickable` in `TextAreaWithFooterType`

* remove references to `FlickableType` in `TextFieldWithHeaderType`

* remove references to `FlickableType` in `SwitcherType`

* remove references to `FlickableType` in `CheckBoxType`

* remove references to `FlickableType` in `CardWithIconsType.qml`

* remove references to `FlickableType` in `BasicButtonType.qml`

* update `ServersListView`

* update `SettingsContainersListView`

* update `InstalledAppsDrawer`

* update `SelectLanguageDrawer`

* update `HomeContainersListView`

* update `HomeSplitTunnelingDrawer`

* fix `PageSetupWizardApiServicesList`

---------

Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com>
2025-08-06 10:35:51 +08:00
KsZnak
eae2936449 Update README links.md [no ci]
Update README links.md
2025-08-04 19:35:45 +01:00
KsZnak
da8ad1f6ba UTM added.md [no ci]
Update README_RU.md
2025-08-04 19:34:12 +01:00
Mitternacht822
5472347969 feature: added warning label when config files have changed in premium configuration files menu (#1718)
* added warning label when config files have changed in premium configuration files display

* moved warning display from PageSettingsApiNativeConfigs.qml to PageSettingsApiServerInfo.qml
2025-08-04 14:13:22 +08:00
Mitternacht822
a43f7a6926 feat: added vpn key to subscription settings page (#1717)
* added subscription key display element to subscription management page

* refactrored KeySubscription item to a new page

* minor fix

* changed PageShareDrawer into PageShareConnection

* added back button

* Removed deprecated ShareConnectionDrawer and migrated to PageShareConnection

* fixed issue when show-connection settings button was not working

* deleted empty lines

* minor fix
2025-08-01 21:02:12 +08:00
Cyril Anisimov
47f917de0b refactoring: change logs time to UTC (#1578)
* update logger to show utc

* add logger to `FocusController`

* add utc timestamps to android logs
2025-08-01 11:56:16 +08:00
Cyril Anisimov
dbeb7edd7a refactor: update ScrollBar policy to use AsNeeded for better usability (#1579)
* refactor: update `ScrollBarType` policy to use
`AsNeeded` for better usability

* add selecting of location settings with Enter

* add handlers to enter push
2025-08-01 11:56:02 +08:00
Mitternacht822
6cede712f5 fix: backup contains platform specific variables (#1646)
* fixed issue with restoring wrong platform specific variables in backup

* fixed wrong line

* fixed issue when restong app split tunneling mode not intended for windows platform

* added field containing application platform to backup file, added feature to clear appsSplitTunneling list from backup file if backup was made on other platform
2025-08-01 11:54:58 +08:00
Mitternacht822
d328739192 fix: add update model after clear profile (#1674)
* fixed issue when ui was not getting update about clearing profile cache right after it

* fixed the problem of not clearing the profile

* refactored reload function in protolocolsModel

* refactored the issue with signal connect in corecontroller
2025-08-01 11:52:11 +08:00
Mitternacht822
d15c0bd962 fix: fixed system tray open site link (#1686)
* added signal-slot connection between corecontroller and systemtraynofificationhandler updating websiteurl

* cleared up the commented lines
2025-08-01 11:50:31 +08:00
Mitternacht822
d53c794936 fix: fixed language load after settings reset (#1735) 2025-08-01 11:47:43 +08:00
Mitternacht822
e5dcb25a4a fix: removed the ability to change location while making connection (#1736) 2025-08-01 11:45:19 +08:00
Mitternacht822
f9002b4f43 refactoring: made start-minimized-option available only when autostart-option is truned on (#1740) 2025-08-01 11:38:36 +08:00
Nethius
0531508a75 feat: added 'clear site list' button (#1747) (#1753)
* feat: added 'clear site list' button (#1747)

* chore: rename 'Export/Import Sites' to 'Additional options'

---------

Co-authored-by: MrMirDan <58086007+MrMirDan@users.noreply.github.com>
2025-08-01 11:37:56 +08:00
Mitternacht822
174e85a20a fix: not restoring parameters for open vpn after scanning server (#1759)
* added lines for restoring settings when scanning server for OpenVPN, OpenVPN over Cloak and OpenVPN over SS protocols

* minor fix

* added functionality to restore config for multiprotocol configsCloak and Shadowsocks
2025-08-01 11:36:52 +08:00
MrMirDan
e9abb6f1e2 fix: mirror links (#1760)
* Instructions links

* amnezia free feature link

* trying fix api instructions page issue

* androidTV link fix

* tv link fix 2
2025-08-01 11:36:30 +08:00
Nethius
5be44f9596 chore: bump version (#1757)
* chore: update link to submodule

* chore: bump version
2025-07-29 12:20:43 +08:00
vladimir.kuznetsov
90efaaff92 chore: bump version 2025-07-29 12:19:54 +08:00
vladimir.kuznetsov
99b554e7c3 chore: update link to submodule 2025-07-29 12:19:27 +08:00
Nethius
ac0ce8a6f6 chore: bump version (#1746) 2025-07-25 23:21:18 +08:00
Yaroslav
9f9da885b7 fix: bundle version added, icon returned (#1745) 2025-07-25 23:03:11 +08:00
Nethius
f51fd2bf3e chore: update link to submodule (#1738) 2025-07-24 10:13:14 +08:00
Nethius
c8378fd32d chore: update link to submodule (#1733) 2025-07-22 19:50:57 +08:00
Nethius
d767214f10 chore: fixed amneziavpn-service version (#1726) 2025-07-17 15:22:21 +08:00
Nethius
e027c504ae chore: bump version and add version to amneziavpn-service (#1725) 2025-07-16 13:49:29 +08:00
MrMirDan
669a95d975 chore: updated amnezia_ru_RU.ts (#1720)
* Update amneziavpn_ru_RU.ts

* Update amneziavpn_ru_RU.ts

* Update amneziavpn_ru_RU.ts

* Deleted corrupted ru translation

* Updated amneziavpn_ru_RU.ts

* Saved amneziavpn_ru_RU.ts

* Rewrite some back on english

* Rewrite small issues

* Rewrite another small issues

* Create deploy_mod.yml

Modificated deploy.yml - removed Linux, IOS and MacOS jobs
Made just for test and learning

* some changes

* deleted my uneccessary file

* new translations
2025-07-16 13:26:49 +08:00
Nethius
a96df5d518 fix: temporarily removed vless for api native configs (#1724) 2025-07-16 13:26:19 +08:00
aiamnezia
c5c81735a0 fix: split tunneling with vless api configs (#1716) 2025-07-16 10:04:49 +08:00
Nethius
c933745707 chore: downgrade qt version for macos cicd build (#1705) 2025-07-10 19:48:03 +08:00
Nethius
6710fd18b3 chore: bump version (#1703) 2025-07-10 19:40:18 +08:00
Yaroslav
1b78a71529 feat: ci/cd for macos signed pkg bundle (#1699)
* Fixing broken ci/cd for macos pkg bundle

* chore: fix cert parsing

* chore: added notarization flag to macos build

* refactor: update certificate import logic in build_macos.sh script

---------

Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com>
2025-07-10 10:04:59 +08:00
Nethius
1909d3c94e chore: bump version (#1701) 2025-07-08 15:11:45 +08:00
Nethius
10a107716c fix: fixed awg 1.5 fields processing for ios (#1700) 2025-07-08 15:06:52 +08:00
Nethius
5445e6637b chore: minor fixes (#1616)
* chore: removed unnecessary qdebug

* fix: return soft and hide strict killswitch
2025-07-08 14:25:03 +08:00
Nethius
2380cd5cfb feat: amneziawg 1.5 support (#1692)
* Version bump 4.2.1.0

* feat: add special handshake params to ui

* feat: finish adding params

* feat: android/ios & fix qml

* chore: fix android impl & update 3rd-prebuilt branch

* chore: trigger build with windows build

* fix: special handshake params to client

* chore: update submodule

* feat: s3, s4

* chore: update submodule

* feat: s3 s4 cont

* fix: kt set

* chore: update submodule

* feat: add default values for s3, s4

* fix: make new parameters optional

* chore: update submodules

* chore: restore translation files

* fix: fixed awg native config import with new junk

* chore: restore translation files

* AWG v1.5 Build

* refactoring: removed s3 s4 fileds from ui part

* chore: update link to amneziawg-apple

---------

Co-authored-by: pokamest <pokamest@gmail.com>
Co-authored-by: Mark Puha <p.mark95@gmail.com>
Co-authored-by: albexk <albexk@proton.me>
Co-authored-by: Mykola Baibuz <mykola.baibuz@gmail.com>
2025-07-07 12:03:25 +08:00
Nethius
42661618dc chore: bump version (#1696) 2025-07-07 10:44:35 +08:00
Nethius
8a7e901d7a Merge pull request #1695 from amnezia-vpn/chore/hide-strict-killswitch
chore: temporarily hide the strict killswitch
2025-07-07 10:42:25 +08:00
vladimir.kuznetsov
f8bea71716 chore: temporarily hide the strict killswitch 2025-07-07 10:26:16 +08:00
Nethius
efcc0b7efc feat: xray api support (#1679)
* refactoring: moved shared code into reusable functions for ApiConfigsController

* feat: add xray support in apiConfigsController

* feat: added a temporary switch for the xray protocol on api settings page

* feat: added supported protocols field processing

* refactoring: moved IsProtocolSelectionSupported to apiAccountInfoModel
2025-07-03 09:58:23 +08:00
Yaroslav
4d17e913b5 feat: native macos installer distribution (#1633)
* Add uninstall option and output pkg

Improve installer mode detection

Fix macOS installer packaging

Fix default selection for uninstall choice

Remove obsolete tar handling and clean script copies

* Improve macOS build script

* fix: update macos firewall and package scripts for better compatibility and cleanup

* Add DeveloperID certificate and improve macOS signing script

Use keychain option for codesign and restore login keychain to list
after signing

* Update build_macos.sh

* feat: add script to quit GUI application during uninstall on macos

* fix: handle macos post-install when app is unpacked into localized folder

* fix: improve post_install script to handle missing service plist and provide error logging
2025-07-03 09:51:11 +08:00
Mykola Baibuz
b341934863 fix: allow secondary DNS usage when AmneziaDNS is disabled (#1583)
* Allow secondary DNS usage when AmneziaDNS is disabled

* Don't setup secondary DNS for OpenVPN with AmneziaDNS

---------

Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com>
2025-07-02 10:16:58 +08:00
Nethius
127f8ed3bb fix: fixed desktop entry version for linux (#1665) 2025-07-02 10:14:56 +08:00
Mitternacht822
9dca80de18 fix: notification not showing when changed some protocols (#1666)
* added notification about disconnecting users after applying changes for SS and Cloak servers pages

* added notification about changing protocol data for server and some minor changes
2025-07-02 10:11:52 +08:00
Mitternacht822
b0a6bcc055 fix: fixed issue when native connection format preserved after switching p… (#1659)
* fixed issue when native connection format preserved after switching protocol

* moved newly added code into handler section
2025-07-02 10:11:22 +08:00
aiamnezia
f0626e2eca fix: delete premium V2 migration link from Free config Settings (#1671)
* delete premium V2 update link from Free config Settings

* Add debug logs

* Add property for checking if server config is premium

* remove debug logs
2025-07-02 10:07:56 +08:00
lunardunno
979ab42c5a feat: OpenSUSE support (#1557)
* LOCK_FILE for zypper

Checking LOCK_FILE for zypper to support OpenSUSE

* Installation for OpenSUSE

Docker installation support for OpenSUSE

* quiet for zypper

* LOCK_CMD variable

Implementing the LOCK_CMD variable for different OS.

* additional exception for "server is busy"

* Replacing and with or

Replacing && with ||

* undo changes to serverController

* rpm.lock

rpm.lock for dnf yum and zypper

* LOCK_CMD

check for dnf

* Added zypper in check_user_in_sudo
2025-06-23 09:34:40 +07:00
lunardunno
e152e84ddc feat: docker pull rate limit check (#1657)
* Docker pull rate limit

* Error code for DockerPullRateLimit

* Extended description Error 213

Extended description for the error 213: Docker Pull Rate Limit

* empty line removed
2025-06-23 09:32:56 +07:00
Mykola Baibuz
2605978889 fix: allow internet traffic for strict mode with split tunnel (#1654) 2025-06-17 19:00:41 +07:00
aiamnezia
a2d30efaab fix: add saving custom server name if it overridden by user (#1581)
* Add saving custom server name if it overridden by user

* clear duplicated code
2025-06-16 21:01:46 +07:00
Nethius
d3715d00ae chore: fixed artifact names (#1635) 2025-06-09 09:17:40 +07:00
Mitternacht822
c37662dbe2 fix: fixed the bug when split tunneling was not preserving after backup for Windows and Android platforms (#1584)
* fixed the bug when split tunneling was not preserving after backup for Windows and Android platforms

* fixed camelCase and setRouteMode() call

* fixed site splitTunneling for all platforms

* fixed issue with not preserving tunneling route mode
2025-06-05 20:48:23 +07:00
Yaroslav
768ca1e73d feat: add support for manual code signing and provisioning profiles for iOS builds (#1605) 2025-06-05 09:21:27 +07:00
Mitternacht822
a20516850c fix: fixed bug when app language was not saved into backup file (#1588) 2025-06-05 09:13:37 +07:00
Mitternacht822
7a203868ec bugfix: fixed bug with not clearing autostart option (#1603) 2025-06-05 09:12:43 +07:00
Mitternacht822
43c3ce9a6e fix: fixed issue with not restoring autostart setting after backup (#1601)
* fixed issue with not restoring autostart setting after backup

* fixed bug when autostart setting was not saving innto backup file and not preserving after backup

* deleted unused lines
2025-06-05 09:08:51 +07:00
Nethius
369e08844f fix: temporarily hide the strict killswitch (#1612) 2025-05-23 22:48:38 +07:00
Nethius
48a5452a65 chore/minor fixes (#1610)
* bugfix: fixed the migration form appearing on app start

* feature: added app version to api requests payload

* chore: remove unused file

* feature: extended logging in service part

* chore: bump version

* chore: update ru translation file
2025-05-23 13:53:55 +07:00
Nethius
c2f9340db6 chore/ru translation (#1606)
* chore: fix ru translation

* chore: bump version
2025-05-21 19:05:08 +07:00
Nethius
a6508e642a bugfix: fixed sending requests if there are no premium v1 keys in the application (#1599) 2025-05-20 12:08:05 +07:00
Nethius
a3e73797c2 chore: bump version (#1598) 2025-05-20 12:02:37 +07:00
Nethius
df7bf204ea chore: minor ui changes (#1597) 2025-05-20 11:58:57 +07:00
MrMirDan
e16243ff55 chore: text translations etc (#1590) 2025-05-20 09:55:24 +07:00
Nethius
e23cbe67ad chore: added account_info request for amfree (#1586) 2025-05-16 13:34:56 +07:00
Nethius
7702f2f74c bugfix: adding gateway to exceptions only if strict killswitch is enabled (#1585) 2025-05-15 20:34:48 +07:00
Nethius
b457ef9a3f feature/premium v1 migration (#1569)
* feature: premium v1 migration

* chore: added stage for macos with new qt version

* chore: downgrade qif version

* chore: minor ui fixes
2025-05-13 11:29:33 +07:00
Mitternacht822
a28ed6a977 feature: added the ability to change port after installing xray (#1556)
* added the ability to change port after installing xray

* fixed issue with not updating server config for xray on windows platform

* fixed some warning in exportcontroller.cpp
2025-05-12 21:14:59 +07:00
Nethius
0c73682cfc chore: update link to submodule (#1544)
* chore: update link to submodule
2025-05-12 19:37:35 +07:00
Mykola Baibuz
7e380b6cfb OpenVPN with system disabled IPv6 (#1563)
* Fix for Win OpenVPN with disabled IPv6 and AllExceptSites Splittunnel mode

* Remove unneeded stuff for ipv6 openvpn
2025-05-12 19:36:25 +07:00
MrMirDan
63b5257986 chore: update text translations and text (#1573) 2025-05-12 14:31:41 +07:00
Nethius
acc4485e81 bugfix: improve malicious string detection for openvpn configs (#1571)
* bugfix: improve malicious string detection for openvpn configs
2025-05-07 14:18:11 +01:00
Mitternacht822
2c44999a31 Fixed bug with not applying changes to subnet address when reinstalling server (#1546)
* fixed bug with not applying changes to subnet address when reinstalling server

* fixed wireguard empty 'subnet address' field after reinstalling and removed showing mask for AWG and wireguard in UI
2025-05-07 20:17:42 +07:00
Mykola Baibuz
e59a48f9f4 Fixes for Windows killswitch (#1565)
* fix: Win OpenVPN with strict mode killswitch

* Fixes for Windows killswitch
2025-05-06 22:11:58 +07:00
aiamnezia
b86356b0cc bugfix: fix ListViewType scrolling (#1550)
* Fix ListViewType scrolling on country selection page

* Disable highlightFollowsCurrentItem for country selection page

* Fix scrolling on container DropDown

* Fix ListView height

* Fix listview layout in DropDownType

* Remove unnecessary MouseArea from country selection page
2025-05-03 13:56:50 +07:00
Mykola Baibuz
f6d7552b58 feature: fillswitch strict mode (#1333)
* Add allowed DNS list for killswitch

* Windows killswitch strict mode backend part

* Killswitch strict mode for Linux and MacOS

* Windows fixes

* feature: Add Kill Switch settings page with strict mode option

* fix windows build after merge

* Refresh killswitch mode when it toggled

* Use HLM to store strictMode flag

* Some Linux updates

* feat: Enhance VerticalRadioButton with improved styling and disabled states

* Refresh killSwitch state update

* Fix build

* refactor: Modularize header components

* Change kill switch radio button styling

* Fix strict kill switch mode handling

* Refactor: Replace HeaderType with new Types for headers in QML pages

* Remove deprecated HeaderType QML component

* Refresh strict mode killswitch after global toggle change

* Implement model, controller and UI for killswitch dns exceptions

* Connect backend part and UI

* Change label text to DNS exceptions

* Remove HeaderType from PageSettingsApiDevices

* Some pretty fixes

* Fix problem with definition sequence of PageSettingsKillSwitchExceptions.pml elements

* Add exclusion method for Windows firewall

* Change ubuntu version in deploy script

* Update ubuntu version in GH actions

* Add confirmation popup for strict killswitch mode

* Add qt standard path for build script

* Add method to killswitch for expanding strickt mode exceptions list and fix allowTrafficTo() for Windows. Also Added cache in KillSwitch class for exceptions

* Add insertion of gateway address to strict killswitch exceptions

* Review fixes

* buildfix and naming

---------

Co-authored-by: aiamnezia <ai@amnezia.org>
2025-05-03 13:54:36 +07:00
Mykola Baibuz
5bd88ac2e9 bugfix: check IPv6 support before IPv6 setup for OpenVPN (#1552) 2025-05-03 13:52:59 +07:00
Mykola Baibuz
94fa5b59f3 bugfix: awg/wg protocol with system disabled IPv6 (#1536)
* fix: AWG/WG protocol with system disabled IPv6

* add check for route prefix type

* fix: ignore IPv6 setup error for Linux

This error can be cased by system disabled IPv6
2025-05-03 13:51:49 +07:00
lunardunno
7169480999 feature: error handling for cgroup (#1486)
* Error for cgroup mountpoint

Added handling of message: cgroup mountpoint does not exist.

* Case for error cgroup

Added case and case description for: Cgroup Mountpoint Does Not Exist

* Case for Runc

Added error handling for Runc, which does not work in cgroup v2.
Changed numbering of new errors.

* stdErr handling fot run_container

Enabling stdErr handling fot run_container.sh

* change for stdErr handling

* Another place to handle the error 211

Another place to handle the error: ServerRuncNotWorkOnCgroupsV2

* test_1

* test 2

* test 3

* Moving error handling

Moving error handling to the right place in the controller.

* Polishing

* Еext correction

Сorrection of description text.
2025-04-23 12:12:23 +07:00
Mikhail Kiselev
c44ce0d77c fix: add missing include (#1541) 2025-04-19 23:21:10 +07:00
Nethius
7fd71a8408 feature: retrieving support info from gateway (#1483)
* feature: retrieving support info from gateway

* feature: added "external-premium" service-type

* chore: fixed external premium visability
2025-04-16 09:58:44 +07:00
DarthSidious007
68db721089 add S3 deploy (#1530) 2025-04-16 09:35:53 +07:00
MrMirDan
a180e12bdf chore: updated ru translation (#1531) 2025-04-12 22:04:34 +07:00
Yaroslav
f3a4a1b1be feat: improve post uninstall script for macos to properly remove application and its components (#1521) 2025-04-11 23:09:12 +07:00
Nethius
6977a8ecbc chore: bump version and update translation files (#1526) 2025-04-11 12:59:06 +07:00
Nethius
d00f64e6ad feature: added export logs button on start page (#1525) 2025-04-11 12:29:28 +07:00
Mykola Baibuz
d5b3da6ba3 Use older Ubuntu version for build job (#1523) 2025-04-11 08:57:56 +07:00
aiamnezia
c245318339 bugfix: empty split tunneling list (#1520)
* Disable split tunneling with empty list

* Fix bug with Amnezia DNS in split tunneling list

* update ubuntu version for linux deploy pipeline

* Fix deploy script
2025-04-10 14:24:33 +07:00
Nethius
b3b0fec2e1 feature: additional logs for proxy bypass (#1518) 2025-04-09 10:47:33 +07:00
Nethius
9d571a4c71 feature: new mirrors support (#1519) 2025-04-08 12:07:31 +07:00
pokamest
f283858490 Merge pull request #1517 from amnezia-vpn/chore/update-go-version
Update go version in actions to 1.24
2025-04-07 21:53:05 +01:00
pokamest
76fe203767 Update go version in actions to 1.24 2025-04-07 18:05:08 +01:00
pokamest
b9a47f2f50 Merge pull request #1516 from amnezia-vpn/feature/openvpn-warning
feature: warning when importing openvpn configurations
2025-04-07 17:59:37 +01:00
vladimir.kuznetsov
27cb17c640 chore: clear warning text before extract 2025-04-07 23:35:24 +08:00
vladimir.kuznetsov
ef8fb89eb3 feature: warning when importing openvpn configurations 2025-04-07 23:30:11 +08:00
Nethius
f1b045f8a8 fixed selecting the default button on PageSetupWizardEasy (#1502) 2025-03-30 12:53:26 +07:00
Anton Sosnin
050066132b Fix iOS initial translation loading (#1477) 2025-03-24 14:35:22 +07:00
Nethius
2a6e6a1e24 chore: bump version (#1485) 2025-03-21 14:12:56 +07:00
Nethius
92689d084c feature/old api proxy (#1484)
* feature: proxy old api requests through gateway

* chore: bump version
2025-03-21 10:25:44 +07:00
lunardunno
00f314039d Patch for user checking. (#1481)
* Direct use of the $HOME variable.

* Sudo check witch variable $HOME.

Direct use of the $HOME variable.

* Changing for Error 208

Changing description and title for error 208

* Revert "Changing for Error 208"

This reverts commit f45624c023.

* Changing for Error 207

Changing description and title for Error 207
2025-03-20 10:24:37 +07:00
lunardunno
fcb75e837d chore: correcting version (#1480)
* Сorrecting version

Correction: return to the correct version

* Correction for SH
2025-03-19 21:51:49 +07:00
Yaroslav
9fbea76b74 There's a common issue of building iOS apps on Qt 6.8 because of new introduced ffmpeg dependency in multimedia Qt package (#1414)
ref: https://community.esri.com/t5/qt-maps-sdk-questions/build-failure-on-ios-with-qt-6-8/m-p/1548701#M5339
2025-03-14 20:40:27 +07:00
lunardunno
b3ff120bcf Checking server user permissions to use sudo (#1442)
* Username if whoami returns an error

Сommand to use home directory name if whoami returns error or is missing for prepare_host.sh.

* Update check_user_in_sudo.sh

Сommand to use home directory name if whoami returns error or is missing for check_user_in_sudo.sh.
Checking server user permissions to use sudo using a package manager or using uname.
Сhecking and redefining the system language.
Checking requirements for sudo users or root in script.

* Cases have been changed and added.

Changed description of the “Server User Not In Sudo” case.
Corrected the name and description of the "ServerPacketManagerError" case. Packet to Package.
Adding a "SudoPackageIsNotPreinstalled" case.
Adding a "ServerUserNotAllowedInSudoers" case.
Adding a "ServerUserPasswordRequired" case.

* Serves errors have been changed and added.

Corrected the name of the "ServerPacketManagerError" error to "ServerPackageManagerError".
Adding a "SudoPackageIsNotPreinstalled" error.
Adding a "ServerUserNotAllowedInSudoers" error.
Adding a "ServerUserPasswordRequired" error.

* Return ServerPacketManagerError

Return to the name "ServerPacketManagerError".

* Added errors handling 

Added new errors' handling to serverController.cpp.
Permission checks are also performed for the root user.

* Update translations

Updating translations for two existing server errors.

* Myanmar translation update

* Update for my_MM.ts

* checking for not allowed

Checking for "not allowed" in stdOut

* Removed "not allowed"

Removed check for "not allowed" in stdOut

* Removed nested launch

Removed nested launch via sudo

* Returned nested launch

Returned nested launch via sudo

* All checks with sudo

Both checks with sudo always run.

* Moved removing timestamp sudo

Removing the sudo timestamp is done every time.

* Checking the user directory

Checking the accessibility of the user's home directory

* Polishing

Изменение порядка обработки ошибок.

* changing detection order 

change the order of detection of inconsistencies:
1. sudo not preinstalled. (if user != root)
2. user not in sudo or wheel group. (if user != root)
3. user's directory is not accessible. (for all)
4. user not allowed in sudoers. (for all)
5. user password required. (for all)

* Packet to Package

* chore: bump version (#1463)

* fix for sh (#1462)

Fix for servers where sh is used as default shell.

* Username if whoami returns an error

Сommand to use home directory name if whoami returns error or is missing for prepare_host.sh.

* Update check_user_in_sudo.sh

Сommand to use home directory name if whoami returns error or is missing for check_user_in_sudo.sh.
Checking server user permissions to use sudo using a package manager or using uname.
Сhecking and redefining the system language.
Checking requirements for sudo users or root in script.

* Cases have been changed and added.

Changed description of the “Server User Not In Sudo” case.
Corrected the name and description of the "ServerPacketManagerError" case. Packet to Package.
Adding a "SudoPackageIsNotPreinstalled" case.
Adding a "ServerUserNotAllowedInSudoers" case.
Adding a "ServerUserPasswordRequired" case.

* Serves errors have been changed and added.

Corrected the name of the "ServerPacketManagerError" error to "ServerPackageManagerError".
Adding a "SudoPackageIsNotPreinstalled" error.
Adding a "ServerUserNotAllowedInSudoers" error.
Adding a "ServerUserPasswordRequired" error.

* Return ServerPacketManagerError

Return to the name "ServerPacketManagerError".

* Update translations

Updating translations for two existing server errors.

* Added errors handling 

Added new errors' handling to serverController.cpp.
Permission checks are also performed for the root user.

* Myanmar translation update

* Update for my_MM.ts

* checking for not allowed

Checking for "not allowed" in stdOut

* Removed "not allowed"

Removed check for "not allowed" in stdOut

* Removed nested launch

Removed nested launch via sudo

* Returned nested launch

Returned nested launch via sudo

* All checks with sudo

Both checks with sudo always run.

* Moved removing timestamp sudo

Removing the sudo timestamp is done every time.

* Checking the user directory

Checking the accessibility of the user's home directory

* Polishing

Изменение порядка обработки ошибок.

* changing detection order 

change the order of detection of inconsistencies:
1. sudo not preinstalled. (if user != root)
2. user not in sudo or wheel group. (if user != root)
3. user's directory is not accessible. (for all)
4. user not allowed in sudoers. (for all)
5. user password required. (for all)

* Undoing unintended changes

Undoing unintended changes.

* Undoing unintended change

Undoing unintended change.

* not allowed to use sudo

The user is not allowed to use sudo on this server.

* Capital letters in the error

Capital letters in the error description.

---------

Co-authored-by: albexk <albexk@proton.me>
2025-03-14 20:39:58 +07:00
paldeflex
9dea98f020 chore: README typo fixes (#1467) 2025-03-10 23:22:09 +07:00
Mykola Baibuz
c4701d4e7a Update XRay for Desktops (#1459)
version 25.3.6
2025-03-10 15:11:26 +07:00
Nethius
48903ca3a1 chore: fixed proxyStorageUrl typo (#1466) 2025-03-09 13:36:21 +07:00
Nethius
0c9fd4aef4 feature: added multiply proxy storage support (#1465) 2025-03-09 13:07:08 +07:00
lunardunno
b2af2e46ac fix for sh (#1462)
Fix for servers where sh is used as default shell.
2025-03-09 12:34:00 +07:00
albexk
efc76a0683 chore: bump version (#1463) 2025-03-09 10:30:43 +07:00
Nethius
c4a553c166 chore: error body processing (#1458) 2025-03-07 10:39:12 +07:00
Cyril Anisimov
69a00b0252 feature: remove the limit of ip addresses = 254 (#1438) 2025-03-06 21:43:47 +07:00
KsZnak
4257c08b43 Update amneziavpn_ru_RU.ts (#1457) 2025-03-06 21:38:42 +07:00
Mykola Baibuz
c9e5b92f79 Remove unneeded flushDns (#1443) 2025-03-05 13:21:39 +07:00
Mykola Baibuz
99818c2ad8 Fixes for native OpenVPN config import (#1444)
* Remote address in OpenVPN config can be host name

* Protocol parameter in OpenVPN config is not mandatory
2025-03-05 13:20:46 +07:00
shiroow
99e3afabad chore: update eng text (#1456)
chore: update eng text
2025-03-05 10:11:31 +07:00
Yaroslav
d3339a7f3a fix: iOS/iPadOS crashes on a start of the app because of there's no keyFrame set (#1448)
So setting one if it's not set.
2025-03-04 18:13:04 +07:00
Nethius
678bfffe49 chore: minor ui fixes (#1446)
* chore: minor ui fixes

* chore: update ru translation file

* bugfix: fixed config update by ttl for gateway configs

* bugfix: fixed proxy bypassing

* chore: minor ui fixes

* chore: update ru translation file

* chore: bump version
2025-03-04 13:33:35 +07:00
Nethius
728b48044c Merge pull request #1440 from amnezia-vpn/feature/subscription-settings-page
feature/subscription settings page
2025-02-28 22:17:43 +07:00
Nethius
7ccbfa48bc bugfix: fixed mobile controllers initialization (#1436)
* bugfix: fixed mobile controllers initialization

* chore: bump version
2025-02-25 22:29:58 +07:00
Nethius
83460bc29b Merge pull request #1395 from amnezia-vpn/feature/subscription-settings-page
feature/subscription settings page
2025-02-24 10:03:17 +03:00
vladimir.kuznetsov
c28e1b468a chore: bump version 2025-02-24 13:41:50 +07:00
vladimir.kuznetsov
abd7fdd19c chore: minor ui fix 2025-02-24 13:39:03 +07:00
vladimir.kuznetsov
2b1ec9c693 chore: added log to see proxy decrypt errors 2025-02-23 14:39:18 +07:00
vladimir.kuznetsov
19fcddfdaf chore: added 404 handling for revoke configs
- added revoke before remove api server for premium v2
2025-02-23 14:26:04 +07:00
Mykola Baibuz
0bca78eca9 Update Windows OpenSSL (#1426)
* Update Windows OpenSSL to 3.0.16 and add shared library for QSslSocket plugin

* chore: update link to submodule 3rd-prebuild

---------

Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com>
2025-02-23 09:59:31 +07:00
Nethius
68046a0b7c Merge pull request #1408 from amnezia-vpn/bugfix/fail_on_win_start
Fix fail during autostart with connect on Windows
2025-02-22 17:41:55 +03:00
vladimir.kuznetsov
d19017f87b chore: minor ui fixes 2025-02-22 14:42:09 +07:00
Mykola Baibuz
46536bc60a change node to IpcProcessTun2SocksReplica 2025-02-21 09:31:10 +02:00
vladimir.kuznetsov
6a424e9858 chore: added link to android tv instruction 2025-02-21 14:16:40 +07:00
vladimir.kuznetsov
8afe50cd87 chore: fixed native config post processing 2025-02-21 14:15:23 +07:00
vladimir.kuznetsov
48980c486e chore: fixed qr code with vpnkey processing 2025-02-21 14:15:03 +07:00
vladimir.kuznetsov
5f6cd282d3 chore: added links to instructions 2025-02-21 14:14:22 +07:00
vladimir.kuznetsov
95121c06e2 feature: added functionality to revoke api configs 2025-02-20 13:44:19 +07:00
vladimir.kuznetsov
c2b17c128d feature: added issued configs info parsing 2025-02-19 22:58:04 +07:00
vladimir.kuznetsov
eda24765e7 feature: added error messages handler 2025-02-19 20:27:15 +07:00
Mykola Baibuz
35e0e146e6 Rewrite timeouts using waitForSource 2025-02-19 14:34:26 +02:00
vladimir.kuznetsov
a5254ac238 chore: fixed qr code display 2025-02-19 14:56:53 +07:00
pokamest
517b5e5ca6 Merge pull request #1413 from amnezia-vpn/Change-links-readme
Update README_RU.md
2025-02-18 08:42:57 +00:00
pokamest
cfeb6cbffd Merge pull request #1412 from amnezia-vpn/Change-link-readme
Update README.md
2025-02-18 08:42:18 +00:00
vladimir.kuznetsov
c128ba981c chore: fixed android build 2025-02-15 15:29:53 +07:00
vladimir.kuznetsov
a1ca994c8b feature: added 409 error handling from server response 2025-02-15 13:58:48 +07:00
vladimir.kuznetsov
52c12940c4 bugfix: fixed visability of share drawer 2025-02-15 13:57:44 +07:00
vladimir.kuznetsov
25d759374c Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into feature/subscription-settings-page 2025-02-15 11:55:03 +07:00
vladimir.kuznetsov
e9250afd2b refactoring: simplified the validity check of the config before connection
- improved project structure
2025-02-15 11:50:42 +07:00
Cyril Anisimov
eb83086d5c apply format to file 2025-02-13 19:26:42 +01:00
Cyril Anisimov
9398e0e695 apply timeouts only for Windows 2025-02-13 19:26:42 +01:00
Cyril Anisimov
915c8f46c5 add timeouts in ipc client init 2025-02-13 19:26:41 +01:00
Nethius
ec132ac96c Merge pull request #1416 from amnezia-vpn/bugfix/android-crush
bugfix: fixed ssl errors handling
2025-02-14 00:27:00 +07:00
vladimir.kuznetsov
101838404e bugfix: fixed possible crush on android 2025-02-14 00:13:57 +07:00
vladimir.kuznetsov
db3164223a feature: added share vpn key to subscription settings page 2025-02-12 12:43:11 +07:00
KsZnak
5a7b5d34fb Update README.md
fix link
2025-02-11 19:54:58 +02:00
KsZnak
9420333c76 Update README_RU.md 2025-02-11 14:14:30 +02:00
KsZnak
f6403fe82e Update README.md 2025-02-11 14:10:03 +02:00
Nethius
c55b025eee Merge pull request #1410 from amnezia-vpn/openvpnadapter-replacement
Openvpnadapter replacement
2025-02-11 09:27:15 +07:00
Yaroslav Yashin
fc6fc26148 feat: remove ios openvpn script and associated cmake configuration 2025-02-10 19:40:14 +01:00
Yaroslav Yashin
48b43ee102 feat: remove OpenVPNAdapter submodule 2025-02-10 19:40:14 +01:00
Yaroslav Yashin
e091020692 refactor: update ios build configuration to use automatic code signing and prebuilt OpenVPNAdapter framework 2025-02-10 19:39:30 +01:00
vladimir.kuznetsov
07baf0ed65 feature: added error handling and minor ui fixes 2025-02-10 15:10:59 +07:00
vladimir.kuznetsov
42d3d9b98a feature: added page for export api native configs 2025-02-07 22:22:14 +07:00
vladimir.kuznetsov
389c1f5327 Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into feature/subscription-settings-page 2025-02-07 10:46:44 +07:00
pokamest
703b9137e0 Merge pull request #1382 from amnezia-vpn/bugfix/ipsec-pfs
Enable PFS for Windows IKEv2
2025-02-06 16:16:38 +01:00
vladimir.kuznetsov
b183a3b232 feature: added pages for subscription settings feature 2025-02-06 15:26:47 +07:00
Pokamest Nikak
f163f0fc1d Update VPN description texts 2025-02-05 23:11:21 +00:00
Pokamest Nikak
3b49d5ca59 Update VPN protocol descriptions 2025-02-04 15:53:40 +00:00
Nethius
236e5ca2e3 Merge pull request #1388 from amnezia-vpn/bugfix/pre-release-hotfixes
chore: returned links for mobile platforms
2025-02-01 00:13:52 +07:00
vladimir.kuznetsov
2f6e28b980 chore: returned links for mobile platforms 2025-02-01 00:10:57 +07:00
Nethius
46d96a8887 Merge pull request #1387 from amnezia-vpn/bugfix/pre-release-hotfixes
bugfix: fixed storeEndpoint parsing
2025-01-31 23:07:33 +07:00
vladimir.kuznetsov
56221881da bugfix: fixed storeEndpoint parsing 2025-01-31 23:01:53 +07:00
vladimir.kuznetsov
3f55f6a629 refactoring: moved gateway interaction functions to a separate class 2025-01-31 14:33:12 +07:00
vladimir.kuznetsov
7c8ae9c311 refactoring: moved api info pages from ServerInfo 2025-01-31 10:35:08 +07:00
Mykola Baibuz
b173dcaa17 Enable PFS for Windows IKEv2 2025-01-28 23:59:50 +02:00
Nethius
da5fe1d766 Merge pull request #1378 from amnezia-vpn/bugfix/pre-release-hotfixes
bugfix/pre-release-hotfixes
2025-01-28 19:22:36 +07:00
vladimir.kuznetsov
a15ea0e8a1 chore: returned the backup page for androidTV 2025-01-28 14:55:08 +07:00
lunardunno
fbbba648c4 Install apparmor (#1379)
Install apparmor
2025-01-27 18:54:21 +00:00
vladimir.kuznetsov
f79bfa9d2e chore: bump version 2025-01-27 12:14:50 +07:00
vladimir.kuznetsov
3011a0e306 chore: fixed again log output with split tunneling info 2025-01-27 11:59:56 +07:00
vladimir.kuznetsov
76640311ab chore: hide "open logs folder" button for mobule platforms 2025-01-26 14:56:46 +07:00
vladimir.kuznetsov
e707471b04 chore: fixed log output with split tunneling info 2025-01-26 14:56:27 +07:00
Nethius
6425700d1c chore: hide site links for ios (#1374) 2025-01-26 14:14:39 +07:00
vladimir.kuznetsov
36045c6694 bugfix: fixed scrolling by keys on PageSettingsApiServerInfo 2025-01-26 14:13:30 +07:00
vladimir.kuznetsov
52ecd6899b bugfix: fixed textFields on PageSetupWizardCredentials 2025-01-24 23:27:01 +07:00
pokamest
49a6a9ed76 Merge pull request #1369 from amnezia-vpn/refactoring/improve-secure-settings 2025-01-19 09:04:51 +01:00
vladimir.kuznetsov
4869429eb6 refactoring: improved the performance of secure_settings 2025-01-19 10:12:30 +07:00
Nethius
956dd6e37a chore: bump version code (#1364) 2025-01-15 12:23:30 +07:00
Nethius
665a2911be bugfix/minor-ui-fixes (#1363)
* bugfix: fixed amfree availability display

* bugfix: fixed selection of exported config type

* chore: hide ad label

* chore: hide ampremium for mobile platforms
2025-01-15 12:04:48 +07:00
KsZnak
1cfa4e0630 Update amneziavpn_ru (#1360)
* Update amneziavpn_ru_RU.ts
2025-01-15 09:31:39 +07:00
pokamest
5bda624576 Update BTC donation address in README_RU 2025-01-14 17:33:50 +00:00
pokamest
d1f0560595 Update donation BTC address 2025-01-14 17:15:01 +00:00
albexk
df07fc1b1f chore: bump version code (#1359) 2025-01-13 22:58:26 +07:00
Nethius
8ca31e0c90 feature/mozilla upstream (#1237)
* cherry-pick 4dfcad96506fb5b88c5bb27342b6d9413fc361c9 from mozilla upstream

* cherry-pick a95fa8c088b9edaff2de18751336942c2d145a9a from mozilla

* cherry-pick commit 4fc1ebbad86a9abcafdc761725a7afd811c8d2d3 from mozilla

* cherry-pick 4dfcad96506fb5b88c5bb27342b6d9413fc361c9 from mozilla upstream

* cherry-pick 22de4fcbd454c64ff496c3380eeaeeb6afff4d64 from mozilla upstream

* cherry-pick 649673be561b66c96367adf379da1545f8838763 from mozilla upstream

* cherry-pick 41bdad34517d0ddaef32139482e5505d92e4b533 from mozilla upstream

* cherry-pick f6e49a85538eaa230d3a8634fa7600966132ccab from mozilla upstream

* cherry-pick 86c585387efa0a09c7937dfe799a90a666404fcd from mozilla upstream

* cherry-pick a18c1fac740469ca3566751b74a16227518630c4 from mozilla upstream

* fixed missing ;

* added excludeLocalNetworks() for linux

* build fixes on windows after cherry-picks

* Add rules for excluded sites splittunell mode

* Fix app splittunell when ipv6 is not setup

* Fix Linux build

---------

Co-authored-by: Mykola Baibuz <mykola.baibuz@gmail.com>
2025-01-13 21:45:06 +07:00
Nethius
f1c6067485 bugfix: fixed work period visibility on page setup api service info (#1355) 2025-01-13 09:55:52 +07:00
KsZnak
ca04c63f5e Update amneziavpn_ru_RU.ts (#1356) 2025-01-13 09:55:41 +07:00
Nethius
89cdd2bece bugfix: fixed site split tunneling mode selector (#1354) 2025-01-12 10:34:43 +07:00
Pokamest Nikak
73d7dfa54f Update translations 2025-01-11 12:49:50 +00:00
albexk
0a5b54a2e4 fix: remove mandatory requirement for android.software.leanback (#1248) 2025-01-09 20:10:42 +07:00
Nethius
e43aa02a5b chore: changed the icon for the settings section (#1348) 2025-01-09 13:33:35 +07:00
Mikhail Kiselev
c3fb62a6ab fix: rewrite linux router dns flusher (#1335)
Co-authored-by: sund3RRR <evenquantity@gamil.com>
2025-01-08 14:38:53 +07:00
Nethius
62f3a339b7 bugfix: ui fixes after merge with android tab navigation branch (#1339)
* bugfix: ui fixes after merge with android tab navigation branch

* bugfix: fix crash on quit

* chore: fix typos

* chore: remove useless comment

* bugfix: fix trigger behavior for `ListViewWithRadioButtonType`

* bugfix: fixed dropdown listview scrolling

* bugfix: fixed amfree availability display

* chore: remove item existence check in triggerCurrentItem function

---------

Co-authored-by: Cyril Anisimov <CyAn84@gmail.com>
2025-01-08 13:12:55 +07:00
Mykola Baibuz
767b14b37a Improve XRay protocol process close (#1318) 2025-01-07 21:52:10 +07:00
Nethius
e7fa160c9c feature: added ad label on page home (#1316)
* feature: added ad label on page home
2025-01-07 10:38:32 +07:00
Vitaly
7350d79c50 feature: WG and AWG: Subnet IP setting change support (#1323)
feature: wg/awg subnet ip setting change support
2025-01-02 14:07:12 +07:00
Mykola Baibuz
86f08554cd fix: check for Linux firewall install before use it (#1328)
* bugfix: check for Linux firewall install before use it

* XRay Linux firewall rules
2024-12-31 10:23:53 +07:00
Andrey Alekseenko
a741186c21 bugfix: Correctly use QProcess::start and QProcess::execute (#1331)
Affected functions (all on Linux/Mac):
 - `RouterLinux::flushDns` was not reloading the DNS manager.
 - `Utils::processIsRunning` was always saying that the process
   is not running when `fullFlag` was set to `false`.
 - `Utils::killProcessByName` was not killing anything.
2024-12-31 10:21:40 +07:00
Cyril Anisimov
6acaab0ffa Improve navigation cpp (#1061)
* add focusController class

* add more key handlers

* add focus navigation to qml

* fixed language selector

* add reverse focus change to FocusController

* add default focus item

* update transitions

* update pages

* add ListViewFocusController

* fix ListView navigation

* update CardType for using with focus navigation

* remove useless key navigation

* remove useless slots, logs, Drawer open and close

* fix reverse focus move on listView

* fix drawer radio buttons selection

* fix drawer layout and focus move

* fix PageSetupWizardProtocolSettings focus move

* fix back navigation on default focus item

* fix crashes after ListView navigation

* fix protocol settings focus move

* fix focus on users on page share

* clean up page share

* fix server rename

* fix page share default server selection

* refactor about page for correct focus move

* fix focus move on list views with header and-or footer

* minor fixes

* fix server list back button handler

* fix spawn signals on switch

* fix share details drawer

* fix drawer open close usage

* refactor listViewFocusController

* refactor focusController to make the logic more
straightforward

* fix focus on notification

* update config page for scrolling with tab

* fix crash on return with esc key

* fix focus navigation in dynamic delegate of list view

* fix focus move on qr code on share page

* refactor page logging settings for focus navigation

* update popup

* Bump version

* Add mandatory requirement for android.software.leanback.

* Fix importing files on TVs

* fix: add separate method for reading files to fix file reading on Android TV

* fix(android): add CHANGE_NETWORK_STATE permission for all Android versions

* Fix connection check for AWG/WG

* chore: minor fixes (#1235)

* fix: add a workaround to open files on Android TV due to lack of SAF

* fix: change the banner format for TV

* refactor: make TvFilePicker activity more sustainable

* fix: add the touch emulation method for Android TV

* fix: null uri processing

* fix: add the touch emulation method for Android TV

* fix: hide UI elements that use file saving

* chore: bump version code

* add `ScrollBarType`

* update initial config page

* refactor credentials setup page to handle the focus navigation

* add `setDelegateIndex` method to `listViewFocusController`

* fix focus behavior on new page/popup

* make minor fixes and clean up

* fix: get rid of the assign function call

* Scrollbar is on if the content is larger than a screen

* Fix selection in language change list

* Update select language list

* update logging settings page

* fix checked item in lists

* fix split tunneling settings

* make unchangable properties readonly

* refactor SwitcherType

* fix hide/unhide password

* `PageShare` readonly properties

* Fix list view focus moving on `PageShare`

* remove manual focus control on `PageShare`

* format `ListViewFocusController`

* format `FocusController`

* add `focusControl` with utility functions for
focus control

* refactor `listViewFocusController` acoording to `focusControl`

* refactor `focusConroller` according to `focusControl`

* add `printSectionName` method to `listViewController`

* remove arrow from `Close application` item

* fix focus movement in `ServersListView`

* `Restore from backup` is visible only on start screen

* `I have nothing` is visible only on start screen

* fix back button on `SelectLanguageDrawer`

* rename `focusControl` to `qmlUtils`

* fix `CMakeLists.txt`

* fix `ScrollBarType`

* fix `PageSetupWizardApiServicesList`

* fix focus movement on dynamic delegates in listView

* refactor `PageSetupWizardProtocols`

* remove comments and clean up

* fix `ListViewWithLabelsType`

* fix `PageProtocolCloakSettings`

* fix `PageSettingsAppSplitTunneling`

* fix `PageDevMenu`

* remove debug output from `FocusController`

* remove debug output from `ListViewFocusController`

* remove debug output from `focusControl`

* `focusControl` => `FocusControl`

---------

Co-authored-by: albexk <albexk@proton.me>
Co-authored-by: Nethius <nethiuswork@gmail.com>
2024-12-31 10:16:52 +07:00
Andrey Alekseenko
212e9b3a91 fix: adding second new VMess links now works (#1325) 2024-12-30 12:45:26 +07:00
Mikhail Kiselev
2bff37efae fix: segmentation violation due to missing return (#1321) 2024-12-28 12:02:14 +07:00
913 changed files with 109282 additions and 34206 deletions

View File

@@ -2,7 +2,7 @@
/client/3rd-prebuild
/client/android
/client/cmake
/client/core/serialization
/client/core/utils/serialization
/client/daemon
/client/fonts
/client/images

View File

@@ -0,0 +1,38 @@
# .github/actions/apple-install-cert/action.yml
name: Setup apple keychain
description: Creates and configures a temporary build keychain
inputs:
keychain-path:
description: Path to the keychain
required: true
keychain-password:
description: Password to the keychain
required: true
cert-base64:
description: Base64-encoded certificate
required: true
cert-password:
description: Certificate password
required: true
runs:
using: composite
steps:
- name: Create keychain
shell: bash
env:
KEYCHAIN_PATH: ${{ inputs.keychain-path }}
KEYCHAIN_PASSWORD: ${{ inputs.keychain-password }}
CERT_BASE64: ${{ inputs.cert-base64 }}
CERT_PASSWORD: ${{ inputs.cert-password }}
run: |
CERT_PATH=$(mktemp /tmp/cert_XXXXXX.p12)
trap "rm -f '$CERT_PATH'" EXIT
echo -n "$CERT_BASE64" | base64 --decode -o "$CERT_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security import "$CERT_PATH" -k "$KEYCHAIN_PATH" -P "$CERT_PASSWORD" -A -t cert -f pkcs12
security set-key-partition-list -S apple-tool:,apple:,codesign: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,57 @@
# .github/actions/apple-setup-keychain/action.yml
name: Setup apple keychain
description: Creates and configures a temporary build keychain
inputs:
keychain-name:
description: Name of the keychain
required: false
default: "ci-amnezia"
keychain-password:
description: The keychain password
required: true
lock-timeout:
description: A timeout after exceeding which the keychain would be locked
required: false
default: "0"
outputs:
keychain-path:
description: "Full path to the keychain created"
value: ${{ steps.setup.outputs.keychain-path }}
keychain-name:
description: "Actual name of the keychain created"
value: ${{ steps.setup.outputs.keychain-name }}
runs:
using: composite
steps:
- name: Setup keychain
id: setup
shell: bash
env:
KEYCHAIN_NAME: ${{ inputs.keychain-name }}
KEYCHAIN_PASSWORD: ${{ inputs.keychain-password }}
LOCK_TIMEOUT: ${{ inputs.lock-timeout }}
run: |
KEYCHAIN_PATH="$HOME/Library/Keychains/$KEYCHAIN_NAME.keychain-db"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
if [[ "$LOCK_TIMEOUT" == "0" ]]; then
security set-keychain-settings "$KEYCHAIN_PATH"
else
security set-keychain-settings -u -t "$LOCK_TIMEOUT" "$KEYCHAIN_PATH"
fi
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security import "${{ github.action_path }}/DeveloperIDG2CA.cer" -k "$KEYCHAIN_PATH" -A
security import "${{ github.action_path }}/AppleWWDRCAG3.cer" -k "$KEYCHAIN_PATH" -A
security list-keychains -d user -s "$KEYCHAIN_PATH"
security default-keychain -s "$KEYCHAIN_PATH"
echo "keychain-name=$KEYCHAIN_NAME" >> $GITHUB_OUTPUT
echo "keychain-path=$KEYCHAIN_PATH" >> $GITHUB_OUTPUT

View File

@@ -0,0 +1,31 @@
# .github/actions/apple-setup-provisioning-profile/action.yml
name: Setup provisioning profiles
description: Decodes and installs provisioning profiles
inputs:
provisioning_profile_base64:
description: Base64-encoded provisioning profile
required: true
runs:
using: composite
steps:
- name: Setup provisioning profile
shell: bash
run: |
PROFILES_DIR="$HOME/Library/MobileDevice/Provisioning Profiles"
TEMP_FILE=$(mktemp)
echo "${{ inputs.provisioning_profile_base64 }}" | base64 --decode > "$TEMP_FILE"
PROFILE_UUID=$(grep UUID -A1 -a "$TEMP_FILE" | grep -io "[-A-F0-9]\{36\}")
if [[ -z "$PROFILE_UUID" ]]; then
echo "Failed to extract UUID from provisioning profile"
rm -f "$TEMP_FILE"
exit 1
fi
mkdir -p "$PROFILES_DIR"
mv "$TEMP_FILE" "$PROFILES_DIR/$PROFILE_UUID.mobileprovision"
echo "Installed profile: $PROFILE_UUID"

File diff suppressed because it is too large Load Diff

View File

@@ -17,9 +17,12 @@ jobs:
QIF_VERSION: 4.5
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Install desktop Qt'

View File

@@ -1,64 +1,41 @@
name: 'Upload a new version'
on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+.[0-9]+'
workflow_dispatch:
inputs:
RELEASE_VERSION:
description: 'Release version (e.g. 1.2.3.4)'
required: true
type: string
jobs:
upload:
Upload-S3:
runs-on: ubuntu-latest
name: upload
steps:
- name: Checkout CMakeLists.txt
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.ref_name }}
ref: ${{ inputs.RELEASE_VERSION }}
sparse-checkout: |
CMakeLists.txt
deploy/deploy_s3.sh
sparse-checkout-cone-mode: false
- name: Verify git tag
run: |
GIT_TAG=${{ github.ref_name }}
CMAKE_TAG=$(grep 'project.*VERSION' CMakeLists.txt | sed -E 's/.* ([0-9]+.[0-9]+.[0-9]+.[0-9]+)$/\1/')
if [[ "$GIT_TAG" == "$CMAKE_TAG" ]]; then
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are the same. Continuing..."
TAG_NAME=${{ inputs.RELEASE_VERSION }}
CMAKE_TAG=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*/\1/')
if [[ "$TAG_NAME" == "$CMAKE_TAG" ]]; then
echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)."
else
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are not the same! Cancelling..."
echo "::error::Mismatch: Git tag ($TAG_NAME) != CMakeLists.txt version ($CMAKE_TAG). Exiting with error..."
exit 1
fi
- name: Download artifacts from the "${{ github.ref_name }}" tag
uses: robinraju/release-downloader@v1.8
- name: Setup Rclone
uses: AnimMouse/setup-rclone@v1
with:
tag: ${{ github.ref_name }}
fileName: "AmneziaVPN_(Linux_|)${{ github.ref_name }}*"
out-file-path: ${{ github.ref_name }}
rclone_config: ${{ secrets.RCLONE_CONFIG }}
- name: Upload beta version
uses: jakejarvis/s3-sync-action@master
if: contains(github.event.base_ref, 'dev')
with:
args: --include "AmneziaVPN*" --delete
env:
AWS_S3_BUCKET: updates
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
SOURCE_DIR: ${{ github.ref_name }}
DEST_DIR: beta/${{ github.ref_name }}
- name: Upload stable version
uses: jakejarvis/s3-sync-action@master
if: contains(github.event.base_ref, 'master')
with:
args: --include "AmneziaVPN*" --delete
env:
AWS_S3_BUCKET: updates
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
SOURCE_DIR: ${{ github.ref_name }}
DEST_DIR: stable/${{ github.ref_name }}
- name: Send dist to S3
run: bash deploy/deploy_s3.sh ${{ inputs.RELEASE_VERSION }}

12
.gitignore vendored
View File

@@ -9,6 +9,7 @@ deploy/build_32/*
deploy/build_64/*
winbuild*.bat
.cache/
.vscode/
# Qt-es
@@ -80,6 +81,7 @@ client/.DS_Store
._.DS_Store
._*
*.dmg
deploy/data/macos/pf/amn.400.allowPIA.conf
# tmp files
*.*~
@@ -133,4 +135,12 @@ client/3rd/ShadowSocks/ss_ios.xcconfig
out/
# CMake files
CMakeFiles/
CMakeFiles/
ios-ne-build.sh
macos-ne-build.sh
macos-signed-build.sh
macos-with-sign-build.sh
DeveloperIdApplicationCertificate.p12
DeveloperIdInstallerCertificate.p12

10
.gitmodules vendored
View File

@@ -1,18 +1,16 @@
[submodule "client/3rd/OpenVPNAdapter"]
path = client/3rd/OpenVPNAdapter
url = https://github.com/amnezia-vpn/OpenVPNAdapter.git
[submodule "client/3rd/qtkeychain"]
path = client/3rd/qtkeychain
url = https://github.com/frankosterfeld/qtkeychain.git
[submodule "client/3rd/SortFilterProxyModel"]
path = client/3rd/SortFilterProxyModel
url = https://github.com/mitchcurtis/SortFilterProxyModel.git
[submodule "client/3rd-prebuilt"]
path = client/3rd-prebuilt
url = https://github.com/amnezia-vpn/3rd-prebuilt
[submodule "client/3rd/amneziawg-apple"]
path = client/3rd/amneziawg-apple
url = https://github.com/amnezia-vpn/amneziawg-apple
[submodule "client/3rd/QSimpleCrypto"]
path = client/3rd/QSimpleCrypto
url = https://github.com/amnezia-vpn/QSimpleCrypto.git
[submodule "client/3rd/qtgamepad"]
path = client/3rd/qtgamepad
url = https://github.com/amnezia-vpn/qtgamepad.git
branch = 6.6

View File

@@ -1,17 +1,34 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
project(${PROJECT} VERSION 4.8.2.4
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.9.0.2)
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
${CMAKE_SOURCE_DIR}/cmake/platform_settings.cmake
${CMAKE_SOURCE_DIR}/cmake/recipes_bootstrap.cmake
${CMAKE_SOURCE_DIR}/cmake/conan_provider.cmake
CACHE STRING "" FORCE)
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
DESCRIPTION "AmneziaVPN"
HOMEPAGE_URL "https://amnezia.org/"
)
# trigger conan to kick off `conan install` globally
find_package(OpenSSL REQUIRED)
if (PREBUILTS_ONLY)
return()
endif()
string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2071)
set(APP_ANDROID_VERSION_CODE 2123)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")
@@ -28,17 +45,34 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten")
endif()
set(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(APPLE AND NOT IOS)
set(CMAKE_OSX_ARCHITECTURES "x86_64")
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(AMN_PF_RULE_IDENTITY "user { root }")
else()
set(AMN_PF_RULE_IDENTITY "group { amnvpn }")
endif()
configure_file(
"${CMAKE_SOURCE_DIR}/deploy/data/pf-templates/amn.400.allowPIA.conf.in"
"${CMAKE_CURRENT_BINARY_DIR}/amn.400.allowPIA.conf"
@ONLY
)
file(COPY_FILE
"${CMAKE_CURRENT_BINARY_DIR}/amn.400.allowPIA.conf"
"${CMAKE_SOURCE_DIR}/deploy/data/macos/pf/amn.400.allowPIA.conf"
ONLY_IF_DIFFERENT
)
endif()
add_subdirectory(client)
if(NOT IOS AND NOT ANDROID)
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
add_subdirectory(service)
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)
endif()
if ((LINUX AND NOT ANDROID) OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (WIN32))
include(${CMAKE_SOURCE_DIR}/cmake/CPack.cmake)
endif()

181
README.md
View File

@@ -9,17 +9,17 @@
### [English]([https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md](https://github.com/amnezia-vpn/amnezia-client/tree/dev?tab=readme-ov-file#)) | [Русский](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md)
[Amnezia](https://amnezia.org) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
[Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org)
### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/kldscp/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
### [Website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
> [!TIP]
> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/kldscp/amnezia.org).
> If the [Amnezia website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror).
<a href="https://amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/kldscp/amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
<a href="https://amnezia.org/en/downloads?utm_source=github&utm_campaign=amnezia_button-readme-en"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/en/downloads&utm_source=github&utm_campaign=amnezia_button-readme-en-mirrow"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
@@ -53,24 +53,14 @@ AmneziaVPN uses several open-source projects to work:
- [OpenSSL](https://www.openssl.org/)
- [OpenVPN](https://openvpn.net/)
- [Shadowsocks](https://shadowsocks.org/)
- [Qt](https://www.qt.io/)
- [LibSsh](https://libssh.org) - forked from Qt Creator
- [LibSsh](https://libssh.org)
- [WireGuard](https://www.wireguard.com/)
- [Xray-core](https://xtls.github.io/en/)
- [Conan](https://conan.io/)
- and more...
## Checking out the source code
Make sure to pull all submodules after checking out the repo.
```bash
git submodule update --init --recursive
```
## Development
Want to contribute? Welcome!
### Help with translations
## Help us with translations
Download the most actual translation files.
@@ -83,109 +73,108 @@ Each *.ts file contains strings for one corresponding language.
Translate or correct some strings in one or multiple *.ts files and commit them back to this repository into the ``client/translations`` folder.
You can do it via a web-interface or any other method you're familiar with.
### Building sources and deployment
## Checking out the source code
Check deploy folder for build scripts.
Make sure to pull all submodules after checking out the repo.
### How to build an iOS app from source code on MacOS
1. First, make sure you have [XCode](https://developer.apple.com/xcode/) installed, at least version 14 or higher.
2. We use QT to generate the XCode project. We need QT version 6.6.2. Install QT for MacOS [here](https://doc.qt.io/qt-6/macos.html) or [QT Online Installer](https://www.qt.io/download-open-source). Required modules:
- MacOS
- iOS
- Qt 5 Compatibility Module
- Qt Shader Tools
- Additional Libraries:
- Qt Image Formats
- Qt Multimedia
- Qt Remote Objects
3. Install CMake if required. We recommend CMake version 3.25. You can install CMake [here](https://cmake.org/download/)
4. You also need to install go >= v1.16. If you don't have it installed already,
download go from the [official website](https://golang.org/dl/) or use Homebrew.
The latest version is recommended. Install gomobile
```bash
export PATH=$PATH:~/go/bin
go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init
git submodule update --init --recursive
```
5. Build the project
## Hacking guide
Want to contribute? Welcome!
### Build requirements
* [`CMake`](https://cmake.org/download/)
* Compiler and underlying build system, depending on the target:
- [Linux] Any of `make` and `gcc`
- [Apple] [`Xcode`](https://developer.apple.com/xcode/) or [`Xcode command line tools`](https://developer.apple.com/xcode/)
- [Windows] [`Visual Studio 2022`](https://aka.ms/vs/17/release/vs_community.exe) or [`VS 2022 Build Tools`](https://aka.ms/vs/17/release/vs_buildtools.exe)
- [Android] [`Android SDK`](#installing-android-sdk) and [`Ninja`](https://ninja-build.org/)
* [`Qt 6.10+`](https://www.qt.io/download-open-source) with the following modules:
- Core module for targeting platform (Desktop/Android/iOS)
- Qt 5 Compatibility module
- Qt Remote Objects
* [`Conan`](https://conan.io/downloads) package manager
- On MacOS is enough just to use `homebrew` or install it in `.venv` in project root
- Other systems must have it in `PATH`
* (Optional) Installer dependencies:
- [Windows/Linux] [`Qt Installer Framework`](https://www.qt.io/download-open-source)
- [Windows] [`WIX toolset`](https://github.com/wixtoolset/wix/releases)
### Building the project using scripts
* Run scripts located in `deploy` directory
* Basically, if dependencies are located in default installation paths, the scripts will find them automatically.
* If they differ, specify them using the following variables:
- `QT_INSTALL_DIR` - Qt root installation folder
- `QT_ROOT_PATH` - Qt framework root directory
- `QIF_ROOT_PATH` - Qt Installer Framework root path
- `ANDROID_HOME` - Path to Android SDK root folder
- and others. Check scripts for more
Unix-like:
```bash
export QT_BIN_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/ios/bin"
export QT_MACOS_ROOT_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/macos"
export QT_IOS_BIN=$QT_BIN_DIR
export PATH=$PATH:~/go/bin
mkdir build-ios
$QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
```
Replace PATH-TO-QT-FOLDER and QT-VERSION to your environment
# Build executables for the host platform
deploy/build.sh
# Or just
deploy/build.sh
If you get `gomobile: command not found` make sure to set PATH to the location
of the bin folder where gomobile was installed. Usually, it's in `GOPATH`.
```bash
export PATH=$(PATH):/path/to/GOPATH/bin
# Build executables and installers for the host platform
deploy/build.sh --installer all
# Build Android APK and AAB
deploy/build.sh -t android --aab
# Call for help
deploy/build.sh -h
```
6. Open the XCode project. You can then run /test/archive/ship the app.
Windows:
```batch
:: Build executables for Windows
deploy/build.bat
If the build fails with the following error
```
make: ***
[$(PROJECTDIR)/client/build/AmneziaVPN.build/Debug-iphoneos/wireguard-go-bridge/goroot/.prepared]
Error 1
```
Add a user-defined variable to both AmneziaVPN and WireGuardNetworkExtension targets' build settings with
key `PATH` and value `${PATH}/path/to/bin/folder/with/go/executable`, e.g. `${PATH}:/usr/local/go/bin`.
:: Build executables with IFW installer for Windows
deploy/build.bat --installer ifw
if the above error persists on your M1 Mac, then most probably you need to install arch based CMake
```
arch -arm64 brew install cmake
:: Build executables with IFW and WIX installer for Windows
deploy/build.bat --installer ifw --installer wix
:: Or just
deploy/build.bat --installer all
```
Build might fail with the "source files not found" error the first time you try it, because the modern XCode build system compiles dependencies in parallel, and some dependencies end up being built after the ones that
require them. In this case, simply restart the build.
### Developing the project in IDEs
## How to build the Android app
* Basically, you can use any IDE that handles CMake and Qt kits properly to run configure and build steps, and to navigate through the code nicely. For example:
- `Qt Creator`
- `Visual Studio Code` with `Qt Extension Pack`
- and so on
_Tested on Mac OS_
* To use `Xcode`, you have to configure project first by using `cmake`. The easiest way to do it is to use `Qt Creator` for configuration. Then open `AmneziaVPN.xcodeproj` file from the build folder by using `Xcode`. Note that none of the files changed are saved - the files actually getting changed in build directory. Copy them manually if necessary
The Android app has the following requirements:
* JDK 11
* Android platform SDK 33
* CMake 3.25.0
* `Android studio` could be used in the same way - just configure the project by using `cmake` manually or by using `Qt Creator`. Open `<build-dir>/client/android-build` in `Android studio` then. Do not forget to copy the changes - everything you do is saved under the build directory actually.
After you have installed QT, QT Creator, and Android Studio, you need to configure QT Creator correctly.
### Installing Android SDK
- Click in the top menu bar on `QT Creator` -> `Preferences` -> `Devices` and select the tab `Android`.
- Set path to JDK 11
- Set path to Android SDK (`$ANDROID_HOME`)
In case you get errors regarding missing SDK or 'SDK manager not running', you cannot fix them by correcting the paths. If you have some spare GBs on your disk, you can let QT Creator install all requirements by choosing an empty folder for `Android SDK location` and clicking on `Set Up SDK`. Be aware: This will install a second Android SDK and NDK on your machine! 
Double-check that the right CMake version is configured:  Click on `QT Creator` -> `Preferences` and click on the side menu on `Kits`. Under the center content view's `Kits` tab, you'll find an entry for `CMake Tool`. If the default selected CMake version is lower than 3.25.0, install on your system CMake >= 3.25.0 and choose `System CMake at <path>` from the drop-down list. If this entry is missing, you either have not installed CMake yet or QT Creator hasn't found the path to it. In that case, click in the preferences window on the side menu item `CMake`, then on the tab `Tools` in the center content view, and finally on the button `Add` to set the path to your installed CMake. 
Please make sure that you have selected Android Platform SDK 33 for your project: click in the main view's side menu on `Projects`, and on the left, you'll see a section `Build & Run` showing different Android build targets. You can select any of them, Amnezia VPN's project setup is designed in a way that all Android targets will be built. Click on the targets submenu item `Build` and scroll in the center content view to `Build Steps`. Click on `Details` at the end of the headline `Build Android APK` (the `Details` button might be hidden in case the QT Creator Window is not running in full screen!). Here we are: Choose `android-33` as `Android Build Platform SDK`.
That's it! You should be ready to compile the project from QT Creator!
### Development flow
After you've hit the build button, QT-Creator copies the whole project to a folder in the repository parent directory. The folder should look something like `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>`.
If you want to develop Amnezia VPNs Android components written in Kotlin, such as components using system APIs, you need to import the generated project in Android Studio with `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>/client/android-build` as the projects root directory. While you should be able to compile the generated project from Android Studio, you cannot work directly in the repository's Android project. So whenever you are confident with your work in the generated project, you'll need to copy and paste the affected files to the corresponding path in the repository's Android project so that you can add and commit your changes!
You may face compiling issues in QT Creator after you've worked in Android Studio on the generated project. Just do a `./gradlew clean` in the generated project's root directory (`<path>/client/android-build/.`) and you should be good to go.
* Android SDK could be installed using the following methods:
- Using `Qt Creator`. Use `Preferences`->`SDKs`
- Using `Android studio`. By default it installs necessary `SDKs` automatically during the installation
- Manually by using `sdk-manager`. Check [this](https://developer.android.com/tools) page for details
## License
GPL v3.0
This project is licensed under the GNU General Public License v3.0 (see LICENSE) and also includes third-party components distributed under their own terms (see THIRD_PARTY_LICENSES.md).
## Donate
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
Bitcoin: bc1q26eevjcg9j0wuyywd2e3uc9cs2w58lpkpjxq6p <br>
Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf <br>
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>

View File

@@ -6,16 +6,16 @@
[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
[AmneziaVPN](https://amnezia.org) — это open sourse VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
[AmneziaVPN](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org)
### [Сайт](https://amnezia.org) | [Зеркало на сайт](https://storage.googleapis.com/kldscp/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
### [Сайт](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
> [!TIP]
> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/kldscp/amnezia.org).
> Если [сайт Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror).
<a href="https://storage.googleapis.com/kldscp/amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/ru/downloads&utm_source=github&utm_campaign=amnezia_button-readme-ru-mirror"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
[Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases)
@@ -30,7 +30,7 @@
- Классические VPN-протоколы: OpenVPN, WireGuard и IKEv2.
- Протоколы с маскировкой трафика (обфускацией): OpenVPN с плагином [Cloak](https://github.com/cbeuw/Cloak), Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) and XRay.
- Поддержка Split Tunneling — добавляйте любые сайты или приложения в список, чтобы включить VPN только для них.
- Поддерживает платформы: Windows, MacOS, Linux, Android, iOS.
- Поддерживает платформы: Windows, macOS, Linux, Android, iOS.
- Поддержка конфигурации протокола AmneziaWG на [бета-прошивке Keenetic](https://docs.keenetic.com/ua/air/kn-1611/en/6319-latest-development-release.html#UUID-186c4108-5afd-c10b-f38a-cdff6c17fab3_section-idm33192196168192-improved).
## Ссылки
@@ -38,10 +38,10 @@
- [https://amnezia.org](https://amnezia.org) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/kldscp/amnezia.org)
- [https://docs.amnezia.org](https://docs.amnezia.org) - Документация
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Канал поддржки в Telegram (Английский)
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Канал поддржки в Telegram (Фарси)
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Канал поддржки в Telegram (Мьянма)
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Канал поддржки в Telegram (Русский)
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Канал поддержки в Telegram (Английский)
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Канал поддержки в Telegram (Фарси)
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Канал поддержки в Telegram (Мьянма)
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Канал поддержки в Telegram (Русский)
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium | [Зеркало](https://storage.googleapis.com/kldscp/vpnpay.io/ru/amnezia-premium\)
## Технологии
@@ -50,23 +50,14 @@ AmneziaVPN использует несколько проектов с откр
- [OpenSSL](https://www.openssl.org/)
- [OpenVPN](https://openvpn.net/)
- [Shadowsocks](https://shadowsocks.org/)
- [Qt](https://www.qt.io/)
- [LibSsh](https://libssh.org)
- [WireGuard](https://www.wireguard.com/)
- [Xray-core](https://xtls.github.io/en/)
- [Conan](https://conan.io/)
- и другие...
## Проверка исходного кода
После клонирования репозитория обязательно загрузите все подмодули.
```bash
git submodule update --init --recursive
```
## Разработка
Хотите внести свой вклад? Добро пожаловать!
### Помощь с переводами
## Помощь с переводами
Загрузите самые актуальные файлы перевода.
@@ -76,90 +67,98 @@ git submodule update --init --recursive
Переведите или исправьте строки в одном или нескольких файлах *.ts и загрузите их обратно в этот репозиторий в папку ``client/translations``. Это можно сделать через веб-интерфейс или любым другим знакомым вам способом.
### Сборка исходного кода и деплой
Проверьте папку deploy для скриптов сборки.
## Проверка исходного кода
### Как собрать iOS-приложение из исходного кода на MacOS
1. Убедитесь, что у вас установлен XCode версии 14 или выше.
2. Для генерации проекта XCode используется QT. Требуется версия QT 6.6.2. Установите QT для MacOS здесь или через QT Online Installer. Необходимые модули:
- MacOS
- iOS
- Модуль совместимости с Qt 5
- Qt Shader Tools
- Дополнительные библиотеки:
- Qt Image Formats
- Qt Multimedia
- Qt Remote Objects
3. Установите CMake, если это необходимо. Рекомендуемая версия — 3.25. Скачать CMake можно здесь.
4. Установите Go версии >= v1.16. Если Go ещё не установлен, скачайте его с [официального сайта](https://golang.org/dl/) или используйте Homebrew. Установите gomobile:
После клонирования репозитория обязательно загрузите все подмодули.
```bash
export PATH=$PATH:~/go/bin
go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init
git submodule update --init --recursive
```
5. Соберите проект:
## Руководство по разработке
Хотите внести свой вклад? Добро пожаловать!
### Требования для сборки
* [`CMake`](https://cmake.org/download/)
* Компилятор и система сборки, в зависимости от таргета:
- [Linux] Любые `make` и `gcc`
- [Apple] [`Xcode`](https://developer.apple.com/xcode/) или [`Xcode command line tools`](https://developer.apple.com/xcode/)
- [Windows] [`Visual Studio 2022`](https://aka.ms/vs/17/release/vs_community.exe) или [`VS 2022 Build Tools`](https://aka.ms/vs/17/release/vs_buildtools.exe)
- [Android] [`Android SDK`](#установка-android-sdk) и [`Ninja`](https://ninja-build.org/)
* [`Qt 6.10+`](https://www.qt.io/download-open-source) со следующими модулями:
- Основные модули для таргета (Desktop/Android/iOS)
- Qt 5 Compatibility module
- Qt Remote Objects
* Пакетный менеджер [`Conan`](https://conan.io/downloads)
- На MacOS достаточно использовать `homebrew` или установить в `.venv` в корень проекта
- Для остальных систем необходимо прописать пути в `PATH`
* (Необязательно) Заивисимости для установщиков:
- [Windows/Linux] [`Qt Installer Framework`](https://www.qt.io/download-open-source)
- [Windows] [`WIX toolset`](https://github.com/wixtoolset/wix/releases)
### Сборка проекта через скрипты
* Запустите скрипты, находящиеся в папке `deploy`
* Если все зависимости установлены в стандартных локациях, скрипт найдёт их самостоятельно
* Если пути отличаются, их нужно явно указать используя:
- `QT_INSTALL_DIR` - корневая папка установки Qt
- `QT_ROOT_PATH` - корневая папка Qt Framework
- `QIF_ROOT_PATH` - корневая папка Qt Installer Framework
- `ANDROID_HOME` - путь к Android SDK
- и другие. Их можно получить из вышеуказанных скриптов
Unix-like:
```bash
export QT_BIN_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/ios/bin"
export QT_MACOS_ROOT_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/macos"
export QT_IOS_BIN=$QT_BIN_DIR
export PATH=$PATH:~/go/bin
mkdir build-ios
$QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
```
Замените <PATH-TO-QT-FOLDER> и <QT-VERSION> на ваши значения.
# Build executables for the host platform
deploy/build.sh
Если появляется ошибка gomobile: command not found, убедитесь, что PATH настроен на папку bin, где установлен gomobile:
```bash
export PATH=$(PATH):/path/to/GOPATH/bin
# Or just
deploy/build.sh
# Build executables and installers for the host platform
deploy/build.sh --installer all
# Build Android APK and AAB
deploy/build.sh -t android --aab
# Call for help
deploy/build.sh -h
```
6. Откройте проект в XCode. Теперь вы можете тестировать, архивировать или публиковать приложение.
Если сборка завершится с ошибкой:
```
make: ***
[$(PROJECTDIR)/client/build/AmneziaVPN.build/Debug-iphoneos/wireguard-go-bridge/goroot/.prepared]
Error 1
```
Добавьте пользовательскую переменную PATH в настройки сборки для целей AmneziaVPN и WireGuardNetworkExtension с ключом `PATH` и значением `${PATH}/path/to/bin/folder/with/go/executable`, e.g. `${PATH}:/usr/local/go/bin`.
Windows:
```batch
:: Build executables for Windows
deploy/build.bat
Если ошибка повторяется на Mac с M1, установите версию CMake для архитектуры ARM:
```
arch -arm64 brew install cmake
:: Build executables with IFW installer for Windows
deploy/build.bat --installer ifw
:: Build executables with IFW and WIX installer for Windows
deploy/build.bat --installer ifw --installer wix
:: Or just
deploy/build.bat --installer all
```
При первой попытке сборка может завершиться с ошибкой source files not found. Это происходит из-за параллельной компиляции зависимостей в XCode. Просто перезапустите сборку.
### Разработка в IDE
* Можно использовать любые IDE которые умеют работать с CMake и находить Qt Kits. Например:
- `Qt Creator`
- `Visual Studio Code` with `Qt Extension Pack`
- и так далее
## Как собрать Android-приложение
Сборка тестировалась на MacOS. Требования:
- JDK 11
- Android SDK 33
- CMake 3.25.0
Установите QT, QT Creator и Android Studio.
Настройте QT Creator:
* Для использования `Xcode` нужно сконфигурировать проект с помощью `cmake`. Самый простой способ это сделать - использовать `Qt Creator` для конфигурации. Затем, нужно открыть файл `AmneziaVPN.xcodeproj` из папки сборки с помощью `Xcode`. Учтите, что никакие файлы фактически не сохраняются - они сохраняются в директории сборки. Если требуется, скопируйте файлы вручную
- В меню QT Creator перейдите в `QT Creator` -> `Preferences` -> `Devices` ->`Android`.
- Укажите путь к JDK 11.
- Укажите путь к Android SDK (`$ANDROID_HOME`)
* `Android studio` может быть использована подобным вышеуказанному способу - нужно использовать `cmake` вручную или через `Qt Creator` для конфигурации. Далее, откройте `<build-dir>/client/android-build` в `Android studio`. Не забудьте скопировать изменённые файлы в папку с исходным кодом - все файлы, изменённые в IDE, сохраняются фактически в папке сборки.
Если вы сталкиваетесь с ошибками, связанными с отсутствием SDK или сообщением «SDK manager not running», их нельзя исправить просто корректировкой путей. Если у вас есть несколько свободных гигабайт на диске, вы можете позволить Qt Creator установить все необходимые компоненты, выбрав пустую папку для расположения Android SDK и нажав кнопку **Set Up SDK**. Учтите: это установит второй Android SDK и NDK на вашем компьютере!
Убедитесь, что настроена правильная версия CMake: перейдите в **Qt Creator -> Preferences** и в боковом меню выберите пункт **Kits**. В центральной части окна, на вкладке **Kits**, найдите запись для инструмента **CMake Tool**. Если выбранная по умолчанию версия CMake ниже 3.25.0, установите на свою систему CMake версии 3.25.0 или выше, а затем выберите опцию **System CMake at <путь>** из выпадающего списка. Если этот пункт отсутствует, это может означать, что вы еще не установили CMake, или Qt Creator не смог найти путь к нему. В таком случае в окне **Preferences** перейдите в боковое меню **CMake**, затем во вкладку **Tools** в центральной части окна и нажмите кнопку **Add**, чтобы указать путь к установленному CMake.
Убедитесь, что для вашего проекта выбрана Android Platform SDK 33: в главном окне на боковой панели выберите пункт **Projects**, и слева вы увидите раздел **Build & Run**, показывающий различные целевые Android-платформы. Вы можете выбрать любую из них, так как настройка проекта Amnezia VPN разработана таким образом, чтобы все Android-цели могли быть собраны. Перейдите в подраздел **Build** и прокрутите центральную часть окна до раздела **Build Steps**. Нажмите **Details** в заголовке **Build Android APK** (кнопка **Details** может быть скрыта, если окно Qt Creator не запущено в полноэкранном режиме!). Вот здесь выберите **android-33** в качестве Android Build Platform SDK.
### Разработка Android-компонентов
После сборки QT Creator копирует проект в отдельную папку, например, `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>`. Для разработки Android-компонентов откройте сгенерированный проект в Android Studio, указав папку `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>/client/android-build` в качестве корневой.
Изменения в сгенерированном проекте нужно вручную перенести в репозиторий. После этого можно коммитить изменения.
Если возникают проблемы со сборкой в QT Creator после работы в Android Studio, выполните команду `./gradlew clean` в корневой папке сгенерированного проекта (`<path>/client/android-build/.`).
### Установка Android SDK
* Android SDK может быть установлен следующими способами:
- Используя `Qt Creator`, через настройки в пунктах `Preferences`->`SDKs`
- Используя `Android studio`. По умолчанию необходимые `SDK` устанавливаются автоматически.
- Вручную, используя `sdk-manager`. Подробности можно найти [здесь](https://developer.android.com/tools)
## Лицензия
@@ -169,7 +168,7 @@ GPL v3.0
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
Bitcoin: bc1q26eevjcg9j0wuyywd2e3uc9cs2w58lpkpjxq6p <br>
Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf <br>
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>

149
THIRD_PARTY_LICENSES.md Normal file
View File

@@ -0,0 +1,149 @@
# Third-Party Licenses
This project is licensed under the GNU General Public License v3.0.
This file lists third-party software components used by this repository.
Each component is distributed under its own license as linked below.
---
## QtKeychain
- Source: https://github.com/frankosterfeld/qtkeychain
- License: BSD License
- License Text: https://www.gnu.org/licenses/license-list.html#ModifiedBSD
---
## QSimpleCrypto
- Source: https://github.com/n1flh31mur/QSimpleCrypto
- License: Apache License 2.0
- License Text: https://github.com/n1flh31mur/QSimpleCrypto/blob/master/LICENSE
---
## SortFilterProxyModel
- Source: https://github.com/oKcerG/SortFilterProxyModel
- License: MIT License
- License Text: https://github.com/oKcerG/SortFilterProxyModel/blob/master/LICENSE
---
## QJsonStruct
- Source: https://github.com/Qv2ray/QJsonStruct
- License: MIT License
- License Text: https://github.com/Qv2ray/QJsonStruct/blob/master/LICENSE
---
## QR Code Generator (qrcodegen)
- Source: https://github.com/nayuki/QR-Code-generator
- License: MIT License
- License Text: https://www.nayuki.io/page/qr-code-generator-library
---
## Qt Gamepad
- Source: https://github.com/qt/qtgamepad
- License: GNU General Public License v3.0 (GPL-3.0)
- License Text: https://www.gnu.org/licenses/gpl-3.0.en.html
---
## AmneziaWG Apple (WireGuard)
- Source: https://github.com/amnezia-vpn/amneziawg-apple
- License: MIT License
- License Text: https://github.com/amnezia-vpn/amneziawg-apple/blob/master/COPYING
---
## AmneziaWG Android
- Source: https://github.com/amnezia-vpn/amneziawg-go
- License: MIT License
- License Text: https://github.com/amnezia-vpn/amneziawg-go/blob/master/LICENSE
---
## Xray Core
- Source: https://github.com/XTLS/Xray-core
- License: Mozilla Public License 2.0 (MPL-2.0)
- License Text: https://github.com/XTLS/Xray-core/blob/main/LICENSE
---
## Cloak
- Source: https://github.com/cbeuw/Cloak
- License: GNU General Public License v3.0 (GPL-3.0)
- License Text: https://github.com/cbeuw/Cloak/blob/master/LICENSE
---
## Shadowsocks
- Source: https://github.com/shadowsocks/shadowsocks-libev
- License: GPL-3.0-or-later
- License Text: http://www.gnu.org/licenses/
---
## OpenSSL
- Source: https://github.com/openssl/openssl
- License: Apache License 2.0
- License Text: https://www.openssl.org/source/license.html
---
## libssh
- Source: https://www.libssh.org/
- License: GNU Lesser General Public License (LGPL)
- License Text: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
---
## OpenVPNAdapter
- Source: https://github.com/ss-abramchuk/OpenVPNAdapter
- License: GNU Affero General Public License v3.0 (AGPL-3.0)
- License Text: https://github.com/ss-abramchuk/OpenVPNAdapter/blob/master/LICENSE
---
## Wintun
- Source: https://www.wintun.net/
- License: Prebuilt Binaries License
- License Text: https://github.com/WireGuard/wintun/blob/master/prebuilt-binaries-license.txt
---
## Mullvad Split Tunnel Driver
- Source: https://github.com/mullvad/win-split-tunnel
- License: GNU General Public License v3.0 (GPL-3.0) and Mozilla Public License Version 2.0
- License Text: https://github.com/mullvad/win-split-tunnel/blob/master/LICENSE-GPL.md https://github.com/mullvad/win-split-tunnel/blob/master/LICENSE-MPL.txt
---
## tun2socks
- Source: https://github.com/eycorsican/go-tun2socks
- License: MIT License
- License Text: https://github.com/eycorsican/go-tun2socks/blob/master/LICENSE
---
## TAP-Windows Driver
- Source: https://github.com/OpenVPN/tap-windows6
- License: tap-windows6 license
- License Text: https://github.com/OpenVPN/tap-windows6/blob/master/COPYING

14
agw-sdk/.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
# Локальные сборки
build/
build-*/
cmake-build-*/
# Conan
CMakeUserPresets.json
conan.lock
# Примеры
examples/dart_smoke/.dart_tool/
examples/dart_smoke/pubspec.lock
examples/c_smoke/smoke
test_package/

133
agw-sdk/CMakeLists.txt Normal file
View File

@@ -0,0 +1,133 @@
cmake_minimum_required(VERSION 3.21)
project(agw LANGUAGES CXX VERSION 0.1.0)
# --- стандарт ---------------------------------------------------------------
# floor C++20 (см. решения в docs/plans/gateway-sdk/README.md). C++23 — opt-in позже.
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE)
endif()
# --- опции ------------------------------------------------------------------
# Когда SDK подключают через add_subdirectory (отладочная сборка в клиенте), по умолчанию НЕ строим
# тесты и НЕ строим shared C-ABI, чтобы не пачкать родительскую сборку. Standalone — всё включено.
if(PROJECT_IS_TOP_LEVEL)
set(_agw_default_aux ON)
else()
set(_agw_default_aux OFF)
endif()
option(AGW_BUILD_TESTS "Build agw-sdk tests" ${_agw_default_aux})
# Режимы зависимостей (Фаза 5): shared-deps — общий OpenSSL из Conan; vendored — бандл.
# На Фазе 1 определяем only-флаг, реальный механизм бандла появится в Фазе 5.
set(AGW_DEPS_MODE "shared-deps" CACHE STRING "Dependency mode: shared-deps | vendored")
# --- зависимости ------------------------------------------------------------
find_package(OpenSSL REQUIRED)
find_package(Threads REQUIRED)
# libcurl (транспорт по умолчанию). Если не найден — SDK собирается без него, а клиент обязан
# передать свой IHttpClient через Config (см. default_client_fallback.cpp).
find_package(CURL QUIET)
# nlohmann/json: предпочтительно из Conan (find_package), иначе — вендоренный single-header
# (он лежит в tests/third_party и нужен только для локальной сборки без Conan).
find_package(nlohmann_json QUIET)
if(NOT nlohmann_json_FOUND)
add_library(agw_nlohmann_fallback INTERFACE)
target_include_directories(agw_nlohmann_fallback INTERFACE
${CMAKE_CURRENT_SOURCE_DIR}/tests/third_party)
add_library(nlohmann_json::nlohmann_json ALIAS agw_nlohmann_fallback)
message(STATUS "agw: using vendored nlohmann/json fallback (tests/third_party)")
endif()
# --- библиотека -------------------------------------------------------------
# Один раз компилируем объекты, из них собираем static (agw) и shared C-ABI (agw_capi).
add_library(agw_obj OBJECT
src/gateway_controller.cpp
src/c_abi.cpp
src/crypto/rng.cpp
src/crypto/aes.cpp
src/crypto/rsa.cpp
src/crypto/hash.cpp
src/http/curl_client.cpp
src/http/default_client_fallback.cpp
src/protocol/request_builder.cpp
src/protocol/response.cpp
src/protocol/error_mapping.cpp
src/failover/bypass_policy.cpp
src/failover/proxy_list.cpp
src/failover/proxy_picker.cpp
src/util/base64.cpp
src/util/uuid.cpp
src/util/json.cpp
src/util/url.cpp
src/util/thread_pool.cpp
)
target_include_directories(agw_obj
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src
)
# PUBLIC, чтобы зависимости и include-пути дотекли до static/shared (и до тестов).
target_link_libraries(agw_obj
PUBLIC OpenSSL::Crypto nlohmann_json::nlohmann_json Threads::Threads
)
# Скрываем всё по умолчанию: наружу торчат только agw_* (AGW_API = visibility default).
set_target_properties(agw_obj PROPERTIES
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN ON
POSITION_INDEPENDENT_CODE ON
)
if(CURL_FOUND)
target_compile_definitions(agw_obj PRIVATE AGW_HAVE_CURL)
target_link_libraries(agw_obj PUBLIC CURL::libcurl)
message(STATUS "agw: libcurl found — default HTTP client enabled")
else()
message(STATUS "agw: libcurl NOT found — default HTTP client disabled (inject IHttpClient via Config)")
endif()
# Статическая библиотека (C++ API) — для наших приложений / тестов.
add_library(agw STATIC)
target_link_libraries(agw PUBLIC agw_obj)
add_library(agw::agw ALIAS agw)
# Shared C-ABI библиотека (для dart:ffi и сторонних). Экспортирует только agw_*.
option(AGW_BUILD_CAPI_SHARED "Build shared C-ABI library (agw_capi)" ${_agw_default_aux})
if(AGW_BUILD_CAPI_SHARED)
add_library(agw_capi SHARED $<TARGET_OBJECTS:agw_obj>)
target_link_libraries(agw_capi PRIVATE agw_obj)
target_compile_definitions(agw_capi PRIVATE AGW_BUILDING_SHARED)
set_target_properties(agw_capi PROPERTIES OUTPUT_NAME agw_capi)
message(STATUS "agw: deps mode = ${AGW_DEPS_MODE} (vendored — статическая линковка/скрытие символов, через Conan)")
endif()
set_target_properties(agw PROPERTIES
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN ON
POSITION_INDEPENDENT_CODE ON
)
# --- установка (только для Conan-пакета / standalone; не при add_subdirectory) ---------------
if(PROJECT_IS_TOP_LEVEL)
include(GNUInstallDirs)
install(TARGETS agw ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
if(AGW_BUILD_CAPI_SHARED)
install(TARGETS agw_capi
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/agw
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
endif()
# --- тесты ------------------------------------------------------------------
if(AGW_BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()

110
agw-sdk/README.md Normal file
View File

@@ -0,0 +1,110 @@
# agw-sdk
Qt-free C++20 транспорт к API-шлюзу Amnezia (вынос `GatewayController`). Узкая поверхность —
`post` (sync/async) поверх крипты, выбора эндпоинта и обхода блокировок. Протокол воспроизводится
байт-в-байт.
План и решения: [../docs/plans/gateway-sdk/](../docs/plans/gateway-sdk/) — начни с
`agw-sdk-tier1-impl-plan.md` и `README.md` (таблица решений).
## Статус
Тир 1, в работе по фазам:
- [x] **Фаза 1** — каркас + крипта на OpenSSL EVP (AES-256-CBC, RSA-PKCS1 v1.5, SHA-512), base64
(std + url), UUID v4, Qt-Indented JSON-сериализатор, golden-тесты крипты.
- [x] **Фаза 2**`IHttpClient`(libcurl) + `Config`/`GatewayController`/`executePost` + sync `post`;
`request_builder`/`response`/`error_mapping`; интеграционный тест через in-process mock-шлюз
(полный round-trip: SDK шифрует → «сервер» расшифровывает → шифрует ответ → SDK расшифровывает).
- [x] **Фаза 3** — failover: `bypass_policy` (`shouldBypassProxy` дословно), `proxy_list`
(S3-пути + prod-расшифровка через `SHA-512(pubkey)`), `proxy_picker` (health-check `lmbd-health`),
встройка в `executePost` с кешем рабочего прокси на инстансе (под мьютексом). Интеграционный тест:
прямой ответ подозрителен → S3 → health → прокси → успех; повторный запрос идёт сразу на кеш.
- [x] **Фаза 4**`util::ThreadPool` (drain в деструкторе), `postAsync`(коллбэк на потоке пула)/
`postFuture`(`std::future`) поверх `executePost`, `CancellationToken` (проверки между шагами
failover + прерывание трансфера через progress-коллбэк curl → `ErrorCode::Cancelled`). Кеш прокси
под мьютексом, пул — последний член Impl (рушится первым, дожидаясь задач). TSan чисто; ASan+UBSan
10/10.
- [x] **Фаза 5** — C-ABI (`include/agw/c_abi.h` + `src/c_abi.cpp`, `agw_*`: создание/уничтожение,
sync/async `post`, токен отмены, освобождение результата; на границе только C-типы). Сборка:
object-библиотека → static `agw` + shared `agw_capi` (экспортирует **только** `agw_*`, остальное
скрыто). C-smoke (чистый C) и Dart-smoke (`dart:ffi`) проходят. `conan create` (shared-deps)
зелёный — пакет с `libagw.a` + `libagw_capi.dylib` + заголовками. Режим `vendored` (статические
зависимости) задан в conanfile (`-o deps_mode=vendored`).
- [~] **Фаза 6** — интеграция в Qt-клиент. Готово: `GatewayController` переписан тонким адаптером
над `agw::GatewayController` (сигнатуры один в один, байт-паритет payload, персистентный клиент на
окружение, `onBeforeRequest` = iOS inet + desktop kill-switch, async через `QPromise`+маршалинг);
проводка сборки (корневой `conanfile` requires `agw-sdk/0.1.0`, `client/cmake/3rdparty.cmake`
линкует `agw::agw`). Осталось (вне этого окружения): Qt-сборка под все платформы, перевод
синхронных вызовов (`subscription`/`servicesCatalog` `executeRequest`) на рабочий поток, регрессия
против dev/prod. См. `docs/plans/gateway-sdk/agw-sdk-tier1-phase6-integration.md`.
## Раскладка
```
include/agw/ публичные заголовки (types, config, client, http, cancellation, c_abi)
src/crypto/ AES, RSA, SHA-512, RNG
src/util/ base64, uuid, json (Qt-Indented), url, thread_pool
src/protocol/ имена полей API, request_builder, response, error_mapping
src/failover/ bypass_policy, proxy_list, proxy_picker
src/http/ curl_client (+ fallback)
src/c_abi.cpp C-ABI обёртка
tests/ unit + golden + integration (+ вендоренный nlohmann для офлайн-сборки)
examples/ c_smoke (чистый C), dart_smoke (dart:ffi)
```
## C-ABI и потребление из Dart/C
Публичный C-заголовок — `include/agw/c_abi.h`. Shared-библиотека `libagw_capi.*` экспортирует только
`agw_*`. Примеры:
```sh
# чистый C
cc -std=c11 -Iinclude examples/c_smoke/smoke.c -Lbuild-local -lagw_capi -o /tmp/agw_smoke
DYLD_LIBRARY_PATH=build-local /tmp/agw_smoke # → код 1105, OK
# Dart (dart:ffi)
cd examples/dart_smoke && dart pub get && dart run # → код 1105, OK
```
## Локальная сборка и тесты (без Conan)
Нужны CMake ≥ 3.21 и OpenSSL 3. nlohmann/json берётся из вендоренного single-header
(`tests/third_party`), если Conan-пакет не найден.
```sh
cmake -S . -B build-local -DOPENSSL_ROOT_DIR=$(brew --prefix openssl@3)
cmake --build build-local -j
ctest --test-dir build-local --output-on-failure
```
Санитайзеры (macOS): TSan — на конкурентных тестах; ASan+UBSan — `detect_leaks=0` (LSan на Darwin
не поддержан):
```sh
cmake -S . -B build-asan -DOPENSSL_ROOT_DIR=$(brew --prefix openssl@3) \
-DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -g -O1" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address,undefined"
cmake --build build-asan -j
ASAN_OPTIONS=detect_leaks=0 ctest --test-dir build-asan --output-on-failure
```
## Сборка через Conan (как в проекте)
```sh
conan create . -o build_tests=True
```
Зависимости: `openssl/3.6.2`, `nlohmann_json/3.11.3` (как в корневом `conanfile.py`); `libcurl`
с Фазы 2.
## Заметки по паритету
Крипта сверена с `client/3rd/QSimpleCrypto` и `gatewayController.cpp`. Ключевое:
- AES-256-CBC, ключ 32 байта, IV генерится 32 — CBC берёт первые 16; salt (8 байт) в локальном AES
не участвует, уходит только в `key_payload`.
- RSA PKCS#1 v1.5 — паддинг рандомный, поэтому `key_payload` **не** воспроизводим байт-в-байт;
golden проверяет его round-trip, а `api_payload` (AES) — точные байты.
- JSON собирается в формате `QJsonDocument::toJson(Indented)`: отступ 4 пробела, завершающий `\n`,
**отсортированные ключи** (это даёт `aes_iv` раньше `aes_key`).

65
agw-sdk/conanfile.py Normal file
View File

@@ -0,0 +1,65 @@
from conan import ConanFile
from conan.tools.cmake import CMake, CMakeToolchain, CMakeDeps, cmake_layout
class AgwSdkConan(ConanFile):
name = "agw-sdk"
version = "0.1.0"
license = "TBD"
description = "AGW SDK — Qt-free C++ transport to the Amnezia API gateway (Tier 1)"
settings = "os", "compiler", "build_type", "arch"
# shared-deps: линкуем общий OpenSSL/curl/nlohmann из Conan (наши приложения).
# vendored: бандлим зависимости статически + скрытие символов (сторонние/standalone).
options = {
"deps_mode": ["shared-deps", "vendored"],
"build_tests": [True, False],
"build_capi_shared": [True, False],
}
default_options = {
"deps_mode": "shared-deps",
"build_tests": False,
"build_capi_shared": True,
}
exports_sources = "CMakeLists.txt", "include/*", "src/*", "tests/*"
def requirements(self):
# Версия OpenSSL совпадает с приложением (корневой conanfile.py) — без второго OpenSSL.
self.requires("openssl/3.6.2")
self.requires("libcurl/8.10.1")
self.requires("nlohmann_json/3.11.3")
def configure(self):
# vendored: тянем статические зависимости, чтобы забандлить их в библиотеку.
if self.options.deps_mode == "vendored":
self.options["openssl"].shared = False
self.options["libcurl"].shared = False
def layout(self):
cmake_layout(self)
def generate(self):
deps = CMakeDeps(self)
deps.generate()
tc = CMakeToolchain(self)
tc.variables["AGW_DEPS_MODE"] = str(self.options.deps_mode)
tc.variables["AGW_BUILD_TESTS"] = bool(self.options.build_tests)
tc.variables["AGW_BUILD_CAPI_SHARED"] = bool(self.options.build_capi_shared)
tc.generate()
def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()
def package(self):
cmake = CMake(self)
cmake.install()
def package_info(self):
self.cpp_info.libs = ["agw"]
self.cpp_info.includedirs = ["include"]
# Потребитель подключает: find_package(agw-sdk) + target agw::agw
self.cpp_info.set_property("cmake_file_name", "agw-sdk")
self.cpp_info.set_property("cmake_target_name", "agw::agw")

View File

@@ -0,0 +1,39 @@
/*
* Чистый C-потребитель C-ABI: доказывает, что agw_* линкуется и работает из C без C++/Qt.
* Детерминированный путь без сети: невалидный публичный ключ → ApiMissingAgwPublicKey (1105).
*
* Сборка (пример, macOS):
* cc -std=c11 -I ../../include smoke.c -L ../../build-local -lagw_capi -o smoke
* DYLD_LIBRARY_PATH=../../build-local ./smoke
*/
#include <stdio.h>
#include <string.h>
#include "agw/c_abi.h"
int main(void)
{
agw_config cfg;
memset(&cfg, 0, sizeof(cfg));
cfg.gateway_endpoint = "gw.example.test";
cfg.agw_public_key_pem = "not a real pem key"; /* → 1105 без обращения к сети */
cfg.request_timeout_msecs = 5000;
agw_client *client = agw_client_create(&cfg);
if (client == NULL) {
printf("FAIL: agw_client_create returned NULL\n");
return 1;
}
agw_response r = agw_client_post(client, "https://%1/api/v1/test", "{\"x\":1}", "", "", NULL);
printf("post error code = %d\n", r.error);
int ok = (r.error == 1105); /* ApiMissingAgwPublicKey */
agw_response_free(&r);
agw_client_destroy(client);
printf(ok ? "OK\n" : "FAIL\n");
return ok ? 0 : 1;
}

View File

@@ -0,0 +1,235 @@
// Dart-демо C-ABI agw-sdk через dart:ffi.
//
// Показывает поток запроса: подключает лог-хук SDK и onBeforeRequest, поэтому печатаются строки
// [agw] (post START -> direct request url -> direct response -> failover -> post DONE) — видно,
// что запрос ушёл и ответ пришёл. Делает синхронный post, а при AGW_ASYNC=1 — ещё и асинхронный
// (agw_client_post_async + NativeCallable.listener: коллбэк прилетает с потока пула SDK).
//
// Конфиг через переменные окружения (все опциональны):
// AGW_GATEWAY хост шлюза с "%1"-подстановкой, напр. "http://gw.dev.amzsvc.com:80/"
// AGW_PUBKEY_FILE путь к PEM ПУБЛИЧНОГО ключа шлюза (по умолчанию — тестовый фикстур)
// AGW_S3_PRIMARY список S3-адресов через запятую (failover)
// AGW_DEV "1" → dev-режим (S3-список открытым текстом)
// AGW_ENDPOINT шаблон пути, по умолчанию "%1v1/services"
// AGW_PAYLOAD JSON тела запроса
// AGW_ASYNC "1" → дополнительно прогнать асинхронный вызов
// AGW_CAPI_LIB путь к libagw_capi.* (иначе ../../build-local)
import 'dart:async';
import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
final class AgwConfig extends Struct {
external Pointer<Utf8> gatewayEndpoint;
external Pointer<Utf8> agwPublicKeyPem;
external Pointer<Pointer<Utf8>> s3Primary;
@Size()
external int s3PrimaryCount;
external Pointer<Pointer<Utf8>> s3Fallback;
@Size()
external int s3FallbackCount;
@Int32()
external int isDevEnvironment;
@Int32()
external int requestTimeoutMsecs;
@Int32()
external int proxyHealthTimeoutMsecs;
@Int32()
external int proxyStorageTimeoutMsecs;
@Int32()
external int threadPoolSize;
external Pointer<Void> onBeforeRequest;
external Pointer<Void> onBeforeRequestUserData;
external Pointer<Void> log;
external Pointer<Void> logUserData;
}
final class AgwResponse extends Struct {
@Int32()
external int error;
external Pointer<Utf8> body;
@Size()
external int bodyLen;
}
typedef _CreateC = Pointer<Void> Function(Pointer<AgwConfig>);
typedef _PostC = AgwResponse Function(Pointer<Void>, Pointer<Utf8>, Pointer<Utf8>,
Pointer<Utf8>, Pointer<Utf8>, Pointer<Void>);
typedef _PostAsyncC = Void Function(Pointer<Void>, Pointer<Utf8>, Pointer<Utf8>,
Pointer<Utf8>, Pointer<Utf8>, Pointer<Void>, Pointer<Void>, Pointer<Void>);
typedef _PostAsyncDart = void Function(Pointer<Void>, Pointer<Utf8>, Pointer<Utf8>,
Pointer<Utf8>, Pointer<Utf8>, Pointer<Void>, Pointer<Void>, Pointer<Void>);
typedef _FreeC = Void Function(Pointer<AgwResponse>);
typedef _FreeDart = void Function(Pointer<AgwResponse>);
typedef _DestroyC = Void Function(Pointer<Void>);
typedef _DestroyDart = void Function(Pointer<Void>);
typedef _LogNative = Void Function(Int32, Pointer<Utf8>, Pointer<Void>);
typedef _BeforeNative = Void Function(Pointer<Utf8>, Pointer<Void>);
typedef _PostCbNative = Void Function(AgwResponse, Pointer<Void>);
const _levels = ['DBG', 'INF', 'WRN', 'ERR'];
void _printLog(String tag, int level, Pointer<Utf8> message) {
final lvl = (level >= 0 && level < _levels.length) ? _levels[level] : '?';
stdout.writeln(' $tag [agw][$lvl] ${message.toDartString()}');
}
class _Cfg {
final Pointer<AgwConfig> ptr;
final List<Pointer<NativeType>> allocs;
_Cfg(this.ptr, this.allocs);
void free() {
for (final p in allocs) {
calloc.free(p);
}
calloc.free(ptr);
}
}
String _libPath() {
final env = Platform.environment['AGW_CAPI_LIB'];
if (env != null) return env;
final base = '${Directory.current.path}/../../build-local';
if (Platform.isMacOS) return '$base/libagw_capi.dylib';
if (Platform.isWindows) return '$base/agw_capi.dll';
return '$base/libagw_capi.so';
}
String _defaultPubKey() {
final f = File('${Directory.current.path}/../../tests/golden/fixtures/test_rsa_pub.pem');
return f.existsSync() ? f.readAsStringSync() : 'not a real pem key';
}
late String gateway, pubKey, payload;
late int isDev;
late List<String> s3List;
_Cfg buildConfig(Pointer<Void> logFn, Pointer<Void> beforeFn) {
final cfg = calloc<AgwConfig>();
final allocs = <Pointer<NativeType>>[];
final gw = gateway.toNativeUtf8();
final pk = pubKey.toNativeUtf8();
allocs.add(gw);
allocs.add(pk);
cfg.ref.gatewayEndpoint = gw;
cfg.ref.agwPublicKeyPem = pk;
cfg.ref.requestTimeoutMsecs = 8000;
cfg.ref.isDevEnvironment = isDev;
cfg.ref.onBeforeRequest = beforeFn;
cfg.ref.log = logFn;
if (s3List.isNotEmpty) {
final arr = calloc<Pointer<Utf8>>(s3List.length);
for (var i = 0; i < s3List.length; i++) {
arr[i] = s3List[i].toNativeUtf8();
allocs.add(arr[i]);
}
cfg.ref.s3Primary = arr;
cfg.ref.s3PrimaryCount = s3List.length;
allocs.add(arr);
}
return _Cfg(cfg, allocs);
}
Future<int> main() async {
final env = Platform.environment;
final lib = DynamicLibrary.open(_libPath());
final create = lib.lookupFunction<_CreateC, _CreateC>('agw_client_create');
final post = lib.lookupFunction<_PostC, _PostC>('agw_client_post');
final postAsync = lib.lookupFunction<_PostAsyncC, _PostAsyncDart>('agw_client_post_async');
final free = lib.lookupFunction<_FreeC, _FreeDart>('agw_response_free');
final destroy = lib.lookupFunction<_DestroyC, _DestroyDart>('agw_client_destroy');
gateway = env['AGW_GATEWAY'] ?? 'http://gw.example.test/';
final pubKeyFile = env['AGW_PUBKEY_FILE'];
pubKey = pubKeyFile != null ? File(pubKeyFile).readAsStringSync() : _defaultPubKey();
final endpoint = env['AGW_ENDPOINT'] ?? '%1v1/services';
payload = env['AGW_PAYLOAD'] ??
'{"os_version":"macos","app_version":"4.9.0","cli_name":"amnezia","app_language":"en"}';
isDev = (env['AGW_DEV'] == '1') ? 1 : 0;
s3List = (env['AGW_S3_PRIMARY'] ?? '')
.split(',')
.map((s) => s.trim())
.where((s) => s.isNotEmpty)
.toList();
stdout.writeln('=== agw-sdk Dart demo ===');
stdout.writeln('gateway=$gateway endpoint=$endpoint dev=$isDev s3primary=${s3List.length}');
stdout.writeln('pubkey=${pubKeyFile ?? "(test fixture)"}');
final endpointC = endpoint.toNativeUtf8();
final payloadC = payload.toNativeUtf8();
final svc = ''.toNativeUtf8();
// ---------- SYNC (коллбэки isolateLocal: ядро sync исполняется на этом потоке) ----------
stdout.writeln('\n--- SYNC post ---');
final logSync = NativeCallable<_LogNative>.isolateLocal(
(int lvl, Pointer<Utf8> m, Pointer<Void> _) => _printLog('[sync]', lvl, m));
final beforeSync = NativeCallable<_BeforeNative>.isolateLocal(
(Pointer<Utf8> h, Pointer<Void> _) =>
stdout.writeln(' [sync] → onBeforeRequest host=${h.toDartString()}'));
final cfgSync = buildConfig(logSync.nativeFunction.cast(), beforeSync.nativeFunction.cast());
final clientSync = create(cfgSync.ptr);
final resp = post(clientSync, endpointC, payloadC, svc, svc, nullptr);
stdout.writeln(' [sync] RESULT errorCode=${resp.error} bodyLen=${resp.bodyLen}');
if (resp.body != nullptr && resp.bodyLen > 0) {
final body = resp.body.toDartString(length: resp.bodyLen);
stdout.writeln(' [sync] body=${body.length > 200 ? "${body.substring(0, 200)}…" : body}');
}
final rp = calloc<AgwResponse>()
..ref.error = resp.error
..ref.body = resp.body
..ref.bodyLen = resp.bodyLen;
free(rp);
calloc.free(rp);
destroy(clientSync);
cfgSync.free();
logSync.close();
beforeSync.close();
// ---------- ASYNC (коллбэки listener: прилетают с потока пула SDK) ----------
if (env['AGW_ASYNC'] == '1') {
stdout.writeln('\n--- ASYNC post (коллбэк с потока пула) ---');
// ВАЖНО: лог-хук/onBeforeRequest сюда НЕ вешаем. Их const char* живут лишь во время вызова на
// потоке пула, а NativeCallable.listener выполняется позже на Dart event-loop → указатель был бы
// висячим. Result-коллбэк безопасен: body выделен в куче и принадлежит вызывающему (мы его освобождаем).
final done = Completer<void>();
final resultCb = NativeCallable<_PostCbNative>.listener((AgwResponse r, Pointer<Void> _) {
stdout.writeln(' [async] CALLBACK (прилетел с потока пула) errorCode=${r.error} bodyLen=${r.bodyLen}');
if (r.body != nullptr && r.bodyLen > 0) {
final body = r.body.toDartString(length: r.bodyLen);
stdout.writeln(' [async] body=${body.length > 200 ? "${body.substring(0, 200)}…" : body}');
}
final p = calloc<AgwResponse>()
..ref.error = r.error
..ref.body = r.body
..ref.bodyLen = r.bodyLen;
free(p);
calloc.free(p);
done.complete();
});
final cfgAsync = buildConfig(nullptr, nullptr); // без лог/before-хуков (см. выше)
final clientAsync = create(cfgAsync.ptr);
stdout.writeln(' [async] post_async отправлен, ждём коллбэк…');
postAsync(clientAsync, endpointC, payloadC, svc, svc, resultCb.nativeFunction.cast(),
nullptr, nullptr);
await done.future; // ждём, пока коллбэк прилетит с потока пула
destroy(clientAsync);
cfgAsync.free();
resultCb.close();
}
calloc.free(endpointC);
calloc.free(payloadC);
calloc.free(svc);
stdout.writeln('\n=== done ===');
return 0;
}

View File

@@ -0,0 +1,9 @@
name: agw_dart_smoke
description: Smoke-вызов C-ABI agw-sdk через dart:ffi.
publish_to: none
environment:
sdk: ">=3.0.0 <4.0.0"
dependencies:
ffi: ^2.1.0

10
agw-sdk/include/agw/agw.h Normal file
View File

@@ -0,0 +1,10 @@
#ifndef AGW_AGW_H
#define AGW_AGW_H
#include "agw/cancellation.h"
#include "agw/gateway_controller.h"
#include "agw/config.h"
#include "agw/http.h"
#include "agw/types.h"
#endif

View File

@@ -0,0 +1,80 @@
#ifndef AGW_C_ABI_H
#define AGW_C_ABI_H
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
#if defined(_WIN32)
#if defined(AGW_BUILDING_SHARED)
#define AGW_API __declspec(dllexport)
#elif defined(AGW_USING_SHARED)
#define AGW_API __declspec(dllimport)
#else
#define AGW_API
#endif
#else
#define AGW_API __attribute__((visibility("default")))
#endif
typedef struct agw_client agw_client;
typedef struct agw_cancel_token agw_cancel_token;
typedef void (*agw_before_request_fn)(const char *host, void *user_data);
typedef void (*agw_log_fn)(int level, const char *message, void *user_data);
typedef struct
{
const char *gateway_endpoint;
const char *agw_public_key_pem;
const char *const *s3_primary_endpoints;
size_t s3_primary_count;
const char *const *s3_fallback_endpoints;
size_t s3_fallback_count;
int is_dev_environment;
int request_timeout_msecs;
int proxy_health_timeout_msecs;
int proxy_storage_timeout_msecs;
int thread_pool_size;
agw_before_request_fn on_before_request;
void *on_before_request_user_data;
agw_log_fn log;
void *log_user_data;
} agw_config;
typedef struct
{
int error;
char *body;
size_t body_len;
} agw_response;
typedef void (*agw_post_callback)(agw_response response, void *user_data);
AGW_API agw_client *agw_client_create(const agw_config *config);
AGW_API void agw_client_destroy(agw_client *client);
AGW_API agw_response agw_client_post(agw_client *client, const char *endpoint, const char *payload,
const char *service_type, const char *user_country_code,
agw_cancel_token *cancel_token);
AGW_API void agw_client_post_async(agw_client *client, const char *endpoint, const char *payload,
const char *service_type, const char *user_country_code, agw_post_callback callback,
void *user_data, agw_cancel_token *cancel_token);
AGW_API void agw_response_free(agw_response *response);
AGW_API agw_cancel_token *agw_cancel_token_create(void);
AGW_API void agw_cancel_token_cancel(agw_cancel_token *token);
AGW_API void agw_cancel_token_destroy(agw_cancel_token *token);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,30 @@
#ifndef AGW_CANCELLATION_H
#define AGW_CANCELLATION_H
#include <atomic>
namespace agw
{
class CancellationToken
{
public:
CancellationToken() = default;
CancellationToken(const CancellationToken &) = delete;
CancellationToken &operator=(const CancellationToken &) = delete;
void cancel() noexcept
{
m_cancelled.store(true, std::memory_order_relaxed);
}
bool isCancelled() const noexcept
{
return m_cancelled.load(std::memory_order_relaxed);
}
private:
std::atomic<bool> m_cancelled { false };
};
}
#endif

View File

@@ -0,0 +1,38 @@
#ifndef AGW_CONFIG_H
#define AGW_CONFIG_H
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include "agw/http.h"
#include "agw/types.h"
namespace agw
{
struct Config
{
std::string gatewayEndpoint;
std::string agwPublicKeyPem;
std::vector<std::string> s3PrimaryEndpoints;
std::vector<std::string> s3FallbackEndpoints;
bool isDevEnvironment = false;
int requestTimeoutMsecs = 12000;
int proxyHealthTimeoutMsecs = 1000;
int proxyStorageTimeoutMsecs = 3000;
int threadPoolSize = 4;
std::function<void(const std::string &host)> onBeforeRequest;
std::function<void(LogLevel, const std::string &message)> log;
std::shared_ptr<IHttpClient> httpClient;
};
}
#endif

View File

@@ -0,0 +1,40 @@
#ifndef AGW_GATEWAY_CONTROLLER_H
#define AGW_GATEWAY_CONTROLLER_H
#include <functional>
#include <future>
#include <memory>
#include <string>
#include "agw/cancellation.h"
#include "agw/config.h"
#include "agw/types.h"
namespace agw {
class GatewayController {
public:
explicit GatewayController(Config config);
~GatewayController();
GatewayController(GatewayController &&) noexcept;
GatewayController &operator=(GatewayController &&) noexcept;
GatewayController(const GatewayController &) = delete;
GatewayController &operator=(const GatewayController &) = delete;
Response post(const std::string &endpoint, const std::string &payload, const FailoverContext &ctx,
CancellationToken *cancel = nullptr);
void postAsync(const std::string &endpoint, const std::string &payload,
std::function<void(Response)> onResult, const FailoverContext &ctx,
CancellationToken *cancel = nullptr);
std::future<Response> postFuture(const std::string &endpoint, const std::string &payload,
const FailoverContext &ctx, CancellationToken *cancel = nullptr);
private:
struct Impl;
std::unique_ptr<Impl> m_impl;
};
}
#endif

View File

@@ -0,0 +1,51 @@
#ifndef AGW_HTTP_H
#define AGW_HTTP_H
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>
namespace agw
{
enum class TransportError {
None = 0,
Timeout,
Canceled,
OperationNotImplemented,
ConnectionError,
};
struct HttpRequest
{
std::string url;
std::string method;
std::string body;
std::vector<std::pair<std::string, std::string>> headers;
int timeoutMsecs = 0;
std::function<bool()> cancelCheck;
};
struct HttpResponse
{
TransportError error = TransportError::None;
std::string errorString;
int httpStatusCode = 0;
bool sslError = false;
std::string body;
};
class IHttpClient
{
public:
virtual ~IHttpClient() = default;
virtual HttpResponse send(const HttpRequest &request) = 0;
};
std::unique_ptr<IHttpClient> makeDefaultHttpClient();
}
#endif

View File

@@ -0,0 +1,55 @@
#ifndef AGW_TYPES_H
#define AGW_TYPES_H
#include <string>
namespace agw
{
enum class ErrorCode : int {
NoError = 0,
Cancelled = 1,
ApiConfigDownloadError = 1100,
ApiConfigAlreadyAdded = 1101,
ApiConfigEmptyError = 1102,
ApiConfigTimeoutError = 1103,
ApiConfigSslError = 1104,
ApiMissingAgwPublicKey = 1105,
ApiConfigDecryptionError = 1106,
ApiServicesMissingError = 1107,
ApiConfigLimitError = 1108,
ApiNotFoundError = 1109,
ApiMigrationError = 1110,
ApiUpdateRequestError = 1111,
ApiSubscriptionExpiredError = 1112,
ApiPurchaseError = 1113,
ApiSubscriptionNotActiveError = 1114,
ApiNoPurchasedSubscriptionsError = 1115,
ApiTrialAlreadyUsedError = 1116,
ApiCaptchaRequiredError = 1117,
ApiCaptchaInvalidError = 1118,
ApiCaptchaRefreshError = 1119,
ApiRateLimitError = 1120,
};
enum class LogLevel : int {
Debug,
Info,
Warning,
Error
};
struct Response
{
ErrorCode error = ErrorCode::NoError;
std::string body;
};
struct FailoverContext
{
std::string serviceType;
std::string userCountryCode;
};
}
#endif

201
agw-sdk/src/c_abi.cpp Normal file
View File

@@ -0,0 +1,201 @@
#include "agw/c_abi.h"
#include <cstdlib>
#include <cstring>
#include <memory>
#include <mutex>
#include <string>
#include <utility>
#include "agw/cancellation.h"
#include "agw/gateway_controller.h"
#include "agw/config.h"
#include "detail/test_hooks.h"
struct agw_client
{
explicit agw_client(agw::Config cfg) : client(std::move(cfg))
{
}
agw::GatewayController client;
};
struct agw_cancel_token
{
agw::CancellationToken token;
};
namespace agw::detail
{
namespace
{
std::mutex g_testHttpMutex;
std::shared_ptr<IHttpClient> g_testHttp;
}
void setNextTestHttpClient(std::shared_ptr<IHttpClient> http)
{
std::lock_guard<std::mutex> lock(g_testHttpMutex);
g_testHttp = std::move(http);
}
std::shared_ptr<IHttpClient> takeNextTestHttpClient()
{
std::lock_guard<std::mutex> lock(g_testHttpMutex);
std::shared_ptr<IHttpClient> h = std::move(g_testHttp);
g_testHttp.reset();
return h;
}
}
namespace
{
std::string cstr(const char *s)
{
return s ? std::string(s) : std::string();
}
agw_response toCResponse(const agw::Response &r)
{
agw_response out;
out.error = static_cast<int>(r.error);
out.body = nullptr;
out.body_len = r.body.size();
char *buf = static_cast<char *>(std::malloc(r.body.size() + 1));
if (buf != nullptr) {
if (!r.body.empty()) {
std::memcpy(buf, r.body.data(), r.body.size());
}
buf[r.body.size()] = '\0';
out.body = buf;
} else {
out.body_len = 0;
}
return out;
}
}
extern "C" {
agw_client *agw_client_create(const agw_config *config)
{
if (config == nullptr) {
return nullptr;
}
agw::Config cfg;
cfg.gatewayEndpoint = cstr(config->gateway_endpoint);
cfg.agwPublicKeyPem = cstr(config->agw_public_key_pem);
for (size_t i = 0; i < config->s3_primary_count; ++i) {
cfg.s3PrimaryEndpoints.push_back(cstr(config->s3_primary_endpoints[i]));
}
for (size_t i = 0; i < config->s3_fallback_count; ++i) {
cfg.s3FallbackEndpoints.push_back(cstr(config->s3_fallback_endpoints[i]));
}
cfg.isDevEnvironment = config->is_dev_environment != 0;
if (config->request_timeout_msecs > 0)
cfg.requestTimeoutMsecs = config->request_timeout_msecs;
if (config->proxy_health_timeout_msecs > 0)
cfg.proxyHealthTimeoutMsecs = config->proxy_health_timeout_msecs;
if (config->proxy_storage_timeout_msecs > 0)
cfg.proxyStorageTimeoutMsecs = config->proxy_storage_timeout_msecs;
if (config->thread_pool_size > 0)
cfg.threadPoolSize = config->thread_pool_size;
if (config->on_before_request != nullptr) {
agw_before_request_fn fn = config->on_before_request;
void *ud = config->on_before_request_user_data;
cfg.onBeforeRequest = [fn, ud](const std::string &host) { fn(host.c_str(), ud); };
}
if (config->log != nullptr) {
agw_log_fn fn = config->log;
void *ud = config->log_user_data;
cfg.log = [fn, ud](agw::LogLevel level, const std::string &msg) { fn(static_cast<int>(level), msg.c_str(), ud); };
}
if (auto http = agw::detail::takeNextTestHttpClient()) {
cfg.httpClient = http;
}
try {
return new agw_client(std::move(cfg));
} catch (...) {
return nullptr;
}
}
void agw_client_destroy(agw_client *client)
{
delete client;
}
agw_response agw_client_post(agw_client *client, const char *endpoint, const char *payload, const char *service_type,
const char *user_country_code, agw_cancel_token *cancel_token)
{
if (client == nullptr) {
agw_response out;
out.error = static_cast<int>(agw::ErrorCode::ApiConfigDownloadError);
out.body = nullptr;
out.body_len = 0;
return out;
}
agw::FailoverContext ctx { cstr(service_type), cstr(user_country_code) };
agw::CancellationToken *tk = cancel_token ? &cancel_token->token : nullptr;
agw::Response r = client->client.post(cstr(endpoint), cstr(payload), ctx, tk);
return toCResponse(r);
}
void agw_client_post_async(agw_client *client, const char *endpoint, const char *payload, const char *service_type,
const char *user_country_code, agw_post_callback callback, void *user_data,
agw_cancel_token *cancel_token)
{
if (client == nullptr || callback == nullptr) {
return;
}
agw::FailoverContext ctx { cstr(service_type), cstr(user_country_code) };
agw::CancellationToken *tk = cancel_token ? &cancel_token->token : nullptr;
client->client.postAsync(
cstr(endpoint), cstr(payload),
[callback, user_data](agw::Response r) {
agw_response cr = toCResponse(r);
callback(cr, user_data);
},
ctx, tk);
}
void agw_response_free(agw_response *response)
{
if (response == nullptr) {
return;
}
std::free(response->body);
response->body = nullptr;
response->body_len = 0;
}
agw_cancel_token *agw_cancel_token_create(void)
{
try {
return new agw_cancel_token();
} catch (...) {
return nullptr;
}
}
void agw_cancel_token_cancel(agw_cancel_token *token)
{
if (token != nullptr) {
token->token.cancel();
}
}
void agw_cancel_token_destroy(agw_cancel_token *token)
{
delete token;
}
}

View File

@@ -0,0 +1,87 @@
#include "aes.h"
#include <memory>
#include <stdexcept>
#include <openssl/evp.h>
namespace agw::crypto
{
namespace
{
using CtxPtr = std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)>;
constexpr int kAes256KeyLen = 32;
constexpr int kAesBlock = 16;
void checkKeyIv(const std::vector<std::uint8_t> &key, const std::vector<std::uint8_t> &iv)
{
if (key.size() != static_cast<std::size_t>(kAes256KeyLen)) {
throw std::runtime_error("agw::crypto::aes: key must be 32 bytes (AES-256)");
}
if (iv.size() < static_cast<std::size_t>(kAesBlock)) {
throw std::runtime_error("agw::crypto::aes: iv must be at least 16 bytes");
}
}
}
std::vector<std::uint8_t> aesEncryptCbc(const std::vector<std::uint8_t> &data, const std::vector<std::uint8_t> &key,
const std::vector<std::uint8_t> &iv)
{
checkKeyIv(key, iv);
CtxPtr ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
if (!ctx) {
throw std::runtime_error("agw::crypto::aes: EVP_CIPHER_CTX_new failed");
}
if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_256_cbc(), nullptr, key.data(), iv.data()) != 1) {
throw std::runtime_error("agw::crypto::aes: EVP_EncryptInit_ex failed");
}
std::vector<std::uint8_t> out(data.size() + kAesBlock);
int len = 0;
if (EVP_EncryptUpdate(ctx.get(), out.data(), &len, data.data(), static_cast<int>(data.size())) != 1) {
throw std::runtime_error("agw::crypto::aes: EVP_EncryptUpdate failed");
}
int total = len;
if (EVP_EncryptFinal_ex(ctx.get(), out.data() + total, &len) != 1) {
throw std::runtime_error("agw::crypto::aes: EVP_EncryptFinal_ex failed");
}
total += len;
out.resize(static_cast<std::size_t>(total));
return out;
}
std::vector<std::uint8_t> aesDecryptCbc(const std::vector<std::uint8_t> &data, const std::vector<std::uint8_t> &key,
const std::vector<std::uint8_t> &iv)
{
checkKeyIv(key, iv);
CtxPtr ctx(EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free);
if (!ctx) {
throw std::runtime_error("agw::crypto::aes: EVP_CIPHER_CTX_new failed");
}
if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_256_cbc(), nullptr, key.data(), iv.data()) != 1) {
throw std::runtime_error("agw::crypto::aes: EVP_DecryptInit_ex failed");
}
std::vector<std::uint8_t> out(data.size() + kAesBlock);
int len = 0;
if (EVP_DecryptUpdate(ctx.get(), out.data(), &len, data.data(), static_cast<int>(data.size())) != 1) {
throw std::runtime_error("agw::crypto::aes: EVP_DecryptUpdate failed");
}
int total = len;
if (EVP_DecryptFinal_ex(ctx.get(), out.data() + total, &len) != 1) {
throw std::runtime_error("agw::crypto::aes: EVP_DecryptFinal_ex failed (bad key/iv/padding)");
}
total += len;
out.resize(static_cast<std::size_t>(total));
return out;
}
}

16
agw-sdk/src/crypto/aes.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef AGW_CRYPTO_AES_H
#define AGW_CRYPTO_AES_H
#include <cstdint>
#include <vector>
namespace agw::crypto
{
std::vector<std::uint8_t> aesEncryptCbc(const std::vector<std::uint8_t> &data, const std::vector<std::uint8_t> &key,
const std::vector<std::uint8_t> &iv);
std::vector<std::uint8_t> aesDecryptCbc(const std::vector<std::uint8_t> &data, const std::vector<std::uint8_t> &key,
const std::vector<std::uint8_t> &iv);
}
#endif

View File

@@ -0,0 +1,59 @@
#include "hash.h"
#include <stdexcept>
#include <openssl/sha.h>
namespace agw::crypto
{
namespace
{
int hexNibble(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
}
std::vector<std::uint8_t> sha512(const std::vector<std::uint8_t> &data)
{
std::vector<std::uint8_t> out(SHA512_DIGEST_LENGTH);
SHA512(data.data(), data.size(), out.data());
return out;
}
std::string toHex(const std::vector<std::uint8_t> &data)
{
static const char *digits = "0123456789abcdef";
std::string out;
out.reserve(data.size() * 2);
for (std::uint8_t b : data) {
out.push_back(digits[b >> 4]);
out.push_back(digits[b & 0x0F]);
}
return out;
}
std::vector<std::uint8_t> fromHex(const std::string &hex)
{
if (hex.size() % 2 != 0) {
throw std::runtime_error("agw::crypto::fromHex: odd-length input");
}
std::vector<std::uint8_t> out;
out.reserve(hex.size() / 2);
for (std::size_t i = 0; i < hex.size(); i += 2) {
const int hi = hexNibble(hex[i]);
const int lo = hexNibble(hex[i + 1]);
if (hi < 0 || lo < 0) {
throw std::runtime_error("agw::crypto::fromHex: invalid hex character");
}
out.push_back(static_cast<std::uint8_t>((hi << 4) | lo));
}
return out;
}
}

17
agw-sdk/src/crypto/hash.h Normal file
View File

@@ -0,0 +1,17 @@
#ifndef AGW_CRYPTO_HASH_H
#define AGW_CRYPTO_HASH_H
#include <cstdint>
#include <string>
#include <vector>
namespace agw::crypto
{
std::vector<std::uint8_t> sha512(const std::vector<std::uint8_t> &data);
std::string toHex(const std::vector<std::uint8_t> &data);
std::vector<std::uint8_t> fromHex(const std::string &hex);
}
#endif

View File

@@ -0,0 +1,20 @@
#include "rng.h"
#include <stdexcept>
#include <openssl/rand.h>
namespace agw::crypto
{
std::vector<std::uint8_t> DefaultRng::bytes(std::size_t n)
{
std::vector<std::uint8_t> out(n);
if (n == 0) {
return out;
}
if (RAND_priv_bytes(out.data(), static_cast<int>(n)) != 1) {
throw std::runtime_error("agw::crypto::DefaultRng: RAND_priv_bytes failed");
}
return out;
}
}

24
agw-sdk/src/crypto/rng.h Normal file
View File

@@ -0,0 +1,24 @@
#ifndef AGW_CRYPTO_RNG_H
#define AGW_CRYPTO_RNG_H
#include <cstddef>
#include <cstdint>
#include <vector>
namespace agw::crypto
{
class IRng
{
public:
virtual ~IRng() = default;
virtual std::vector<std::uint8_t> bytes(std::size_t n) = 0;
};
class DefaultRng : public IRng
{
public:
std::vector<std::uint8_t> bytes(std::size_t n) override;
};
}
#endif

111
agw-sdk/src/crypto/rsa.cpp Normal file
View File

@@ -0,0 +1,111 @@
#include "rsa.h"
#include <memory>
#include <stdexcept>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
namespace agw::crypto
{
namespace
{
using BioPtr = std::unique_ptr<BIO, decltype(&BIO_free)>;
using PkeyPtr = std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>;
using PkeyCtxPtr = std::unique_ptr<EVP_PKEY_CTX, decltype(&EVP_PKEY_CTX_free)>;
PkeyPtr loadPublicKey(const std::string &pem)
{
BioPtr bio(BIO_new_mem_buf(pem.data(), static_cast<int>(pem.size())), BIO_free);
if (!bio) {
throw std::runtime_error("agw::crypto::rsa: BIO_new_mem_buf failed");
}
EVP_PKEY *raw = nullptr;
if (!PEM_read_bio_PUBKEY(bio.get(), &raw, nullptr, nullptr)) {
throw std::runtime_error("agw::crypto::rsa: PEM_read_bio_PUBKEY failed");
}
return PkeyPtr(raw, EVP_PKEY_free);
}
PkeyPtr loadPrivateKey(const std::string &pem)
{
BioPtr bio(BIO_new_mem_buf(pem.data(), static_cast<int>(pem.size())), BIO_free);
if (!bio) {
throw std::runtime_error("agw::crypto::rsa: BIO_new_mem_buf failed");
}
EVP_PKEY *raw = nullptr;
if (!PEM_read_bio_PrivateKey(bio.get(), &raw, nullptr, nullptr)) {
throw std::runtime_error("agw::crypto::rsa: PEM_read_bio_PrivateKey failed");
}
return PkeyPtr(raw, EVP_PKEY_free);
}
}
std::vector<std::uint8_t> rsaEncryptPublicPkcs1(const std::vector<std::uint8_t> &plaintext,
const std::string &publicKeyPem)
{
PkeyPtr key = loadPublicKey(publicKeyPem);
PkeyCtxPtr ctx(EVP_PKEY_CTX_new(key.get(), nullptr), EVP_PKEY_CTX_free);
if (!ctx) {
throw std::runtime_error("agw::crypto::rsa: EVP_PKEY_CTX_new failed");
}
if (EVP_PKEY_encrypt_init(ctx.get()) != 1) {
throw std::runtime_error("agw::crypto::rsa: EVP_PKEY_encrypt_init failed");
}
if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_PADDING) != 1) {
throw std::runtime_error("agw::crypto::rsa: set_rsa_padding failed");
}
std::size_t outLen = 0;
if (EVP_PKEY_encrypt(ctx.get(), nullptr, &outLen, plaintext.data(), plaintext.size()) != 1) {
throw std::runtime_error("agw::crypto::rsa: EVP_PKEY_encrypt (size) failed");
}
std::vector<std::uint8_t> out(outLen);
if (EVP_PKEY_encrypt(ctx.get(), out.data(), &outLen, plaintext.data(), plaintext.size()) != 1) {
throw std::runtime_error("agw::crypto::rsa: EVP_PKEY_encrypt failed");
}
out.resize(outLen);
return out;
}
bool rsaPublicKeyValid(const std::string &publicKeyPem)
{
try {
loadPublicKey(publicKeyPem);
return true;
} catch (...) {
return false;
}
}
std::vector<std::uint8_t> rsaDecryptPrivatePkcs1(const std::vector<std::uint8_t> &ciphertext,
const std::string &privateKeyPem)
{
PkeyPtr key = loadPrivateKey(privateKeyPem);
PkeyCtxPtr ctx(EVP_PKEY_CTX_new(key.get(), nullptr), EVP_PKEY_CTX_free);
if (!ctx) {
throw std::runtime_error("agw::crypto::rsa: EVP_PKEY_CTX_new failed");
}
if (EVP_PKEY_decrypt_init(ctx.get()) != 1) {
throw std::runtime_error("agw::crypto::rsa: EVP_PKEY_decrypt_init failed");
}
if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_PADDING) != 1) {
throw std::runtime_error("agw::crypto::rsa: set_rsa_padding failed");
}
std::size_t outLen = 0;
if (EVP_PKEY_decrypt(ctx.get(), nullptr, &outLen, ciphertext.data(), ciphertext.size()) != 1) {
throw std::runtime_error("agw::crypto::rsa: EVP_PKEY_decrypt (size) failed");
}
std::vector<std::uint8_t> out(outLen);
if (EVP_PKEY_decrypt(ctx.get(), out.data(), &outLen, ciphertext.data(), ciphertext.size()) != 1) {
throw std::runtime_error("agw::crypto::rsa: EVP_PKEY_decrypt failed");
}
out.resize(outLen);
return out;
}
}

19
agw-sdk/src/crypto/rsa.h Normal file
View File

@@ -0,0 +1,19 @@
#ifndef AGW_CRYPTO_RSA_H
#define AGW_CRYPTO_RSA_H
#include <cstdint>
#include <string>
#include <vector>
namespace agw::crypto
{
std::vector<std::uint8_t> rsaEncryptPublicPkcs1(const std::vector<std::uint8_t> &plaintext,
const std::string &publicKeyPem);
std::vector<std::uint8_t> rsaDecryptPrivatePkcs1(const std::vector<std::uint8_t> &ciphertext,
const std::string &privateKeyPem);
bool rsaPublicKeyValid(const std::string &publicKeyPem);
}
#endif

View File

@@ -0,0 +1,14 @@
#ifndef AGW_DETAIL_TEST_HOOKS_H
#define AGW_DETAIL_TEST_HOOKS_H
#include <memory>
#include "agw/http.h"
namespace agw::detail
{
void setNextTestHttpClient(std::shared_ptr<IHttpClient> http);
std::shared_ptr<IHttpClient> takeNextTestHttpClient();
}
#endif

View File

@@ -0,0 +1,100 @@
#include "failover/bypass_policy.h"
#include "protocol/keys.h"
#include "util/json.h"
namespace agw::failover
{
namespace
{
constexpr const char *kPattern1 = "No active configuration found for";
constexpr const char *kPattern2 = "No non-revoked public key found for";
constexpr const char *kPattern3 = "Account not found.";
constexpr const char *kPatternQrSessionNotFound = "QR session not found";
constexpr const char *kPatternSessionNotFound = "Session not found";
constexpr const char *kUpdateRequestPattern = "client version update is required";
constexpr const char *kUnprocessableSubscriptionMessage =
"Failed to retrieve subscription information. Is it activated?";
constexpr int kNotFound = 404;
constexpr int kNotImplemented = 501;
constexpr int kPaymentRequired = 402;
constexpr int kConflict = 409;
constexpr int kRequestTimeout = 408;
constexpr int kUnprocessableEntity = 422;
bool contains(const std::string &body, const char *needle)
{
return body.find(needle) != std::string::npos;
}
std::string trim(const std::string &s)
{
std::size_t b = 0, e = s.size();
while (b < e && (s[b] == ' ' || s[b] == '\t' || s[b] == '\n' || s[b] == '\r'))
++b;
while (e > b && (s[e - 1] == ' ' || s[e - 1] == '\t' || s[e - 1] == '\n' || s[e - 1] == '\r'))
--e;
return s.substr(b, e - b);
}
}
bool shouldBypassProxy(TransportError transportError, const std::string &decryptedBody, bool decryptionSuccessful)
{
if (!decryptionSuccessful) {
return true;
}
int apiHttpStatus = -1;
std::string apiErrorMessage;
try {
util::Json obj = util::Json::parse(decryptedBody);
if (obj.is_object()) {
if (auto it = obj.find(protocol::keys::httpStatus); it != obj.end() && it->is_number_integer()) {
apiHttpStatus = it->get<int>();
}
if (auto it = obj.find(protocol::keys::message); it != obj.end() && it->is_string()) {
apiErrorMessage = trim(it->get<std::string>());
}
}
} catch (...) {
}
if (transportError == TransportError::Canceled || transportError == TransportError::Timeout) {
return true;
}
if (contains(decryptedBody, "html")) {
return true;
}
if (apiHttpStatus == kRequestTimeout) {
return false;
}
if (apiHttpStatus == kNotFound) {
if (contains(decryptedBody, kPattern1) || contains(decryptedBody, kPattern2)
|| contains(decryptedBody, kPattern3) || contains(decryptedBody, kPatternQrSessionNotFound)
|| contains(decryptedBody, kPatternSessionNotFound)) {
return false;
}
return true;
}
if (apiHttpStatus == kNotImplemented) {
if (contains(decryptedBody, kUpdateRequestPattern)) {
return false;
}
return true;
}
if (apiHttpStatus == kConflict) {
return false;
}
if (apiHttpStatus == kPaymentRequired) {
return false;
}
if (apiHttpStatus == kUnprocessableEntity) {
return apiErrorMessage != kUnprocessableSubscriptionMessage;
}
if (transportError != TransportError::None) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,13 @@
#ifndef AGW_FAILOVER_BYPASS_POLICY_H
#define AGW_FAILOVER_BYPASS_POLICY_H
#include <string>
#include "agw/http.h"
namespace agw::failover
{
bool shouldBypassProxy(TransportError transportError, const std::string &decryptedBody, bool decryptionSuccessful);
}
#endif

View File

@@ -0,0 +1,71 @@
#include "failover/proxy_list.h"
#include "crypto/aes.h"
#include "crypto/hash.h"
#include "util/base64.h"
#include "util/json.h"
namespace agw::failover
{
namespace
{
void appendStorageUrls(const std::vector<std::string> &baseUrls, const FailoverContext &ctx,
std::vector<std::string> &target)
{
if (!ctx.serviceType.empty()) {
const std::string token = "endpoints-" + ctx.serviceType + "-" + ctx.userCountryCode;
const std::string encoded =
util::base64UrlEncodeNoPad(std::vector<std::uint8_t>(token.begin(), token.end()));
for (const auto &base : baseUrls) {
target.push_back(base + encoded + ".json");
}
}
for (const auto &base : baseUrls) {
target.push_back(base + "endpoints.json");
}
}
std::vector<std::string> parseEndpointsArray(const std::string &json)
{
std::vector<std::string> out;
try {
util::Json doc = util::Json::parse(json);
if (doc.is_array()) {
for (const auto &el : doc) {
if (el.is_string()) {
out.push_back(el.get<std::string>());
}
}
}
} catch (...) {
}
return out;
}
}
std::vector<std::string> buildStorageUrls(const std::vector<std::string> &primaryBaseUrls,
const std::vector<std::string> &fallbackBaseUrls,
const FailoverContext &ctx)
{
std::vector<std::string> result;
appendStorageUrls(primaryBaseUrls, ctx, result);
appendStorageUrls(fallbackBaseUrls, ctx, result);
return result;
}
std::vector<std::string> decodeProxyList(const std::string &body, bool isDevEnvironment, const std::string &pubKeyPem)
{
if (isDevEnvironment) {
return parseEndpointsArray(body);
}
const std::vector<std::uint8_t> pubBytes(pubKeyPem.begin(), pubKeyPem.end());
const std::string h = crypto::toHex(crypto::sha512(pubBytes));
const std::vector<std::uint8_t> key = crypto::fromHex(h.substr(0, 64));
const std::vector<std::uint8_t> iv = crypto::fromHex(h.substr(64, 32));
const std::vector<std::uint8_t> cipher = util::base64Decode(body);
const std::vector<std::uint8_t> plain = crypto::aesDecryptCbc(cipher, key, iv);
return parseEndpointsArray(std::string(plain.begin(), plain.end()));
}
}

View File

@@ -0,0 +1,19 @@
#ifndef AGW_FAILOVER_PROXY_LIST_H
#define AGW_FAILOVER_PROXY_LIST_H
#include <string>
#include <vector>
#include "agw/types.h"
namespace agw::failover
{
std::vector<std::string> buildStorageUrls(const std::vector<std::string> &primaryBaseUrls,
const std::vector<std::string> &fallbackBaseUrls,
const FailoverContext &ctx);
std::vector<std::string> decodeProxyList(const std::string &body, bool isDevEnvironment,
const std::string &pubKeyPem);
}
#endif

View File

@@ -0,0 +1,21 @@
#include "failover/proxy_picker.h"
namespace agw::failover
{
std::string pickHealthyProxy(IHttpClient &http, const std::vector<std::string> &proxyUrls, int timeoutMsecs)
{
for (const auto &proxy : proxyUrls) {
HttpRequest req;
req.url = proxy + "lmbd-health";
req.method = "GET";
req.headers = { { "Content-Type", "application/json" } };
req.timeoutMsecs = timeoutMsecs;
const HttpResponse resp = http.send(req);
if (resp.error == TransportError::None && !resp.sslError) {
return proxy;
}
}
return { };
}
}

View File

@@ -0,0 +1,14 @@
#ifndef AGW_FAILOVER_PROXY_PICKER_H
#define AGW_FAILOVER_PROXY_PICKER_H
#include <string>
#include <vector>
#include "agw/http.h"
namespace agw::failover
{
std::string pickHealthyProxy(IHttpClient &http, const std::vector<std::string> &proxyUrls, int timeoutMsecs);
}
#endif

View File

@@ -0,0 +1,364 @@
#include "agw/gateway_controller.h"
#include <algorithm>
#include <chrono>
#include <functional>
#include <future>
#include <memory>
#include <mutex>
#include <random>
#include <sstream>
#include <string>
#include <thread>
#include <utility>
#include <vector>
#include "crypto/rng.h"
#include "failover/bypass_policy.h"
#include "failover/proxy_list.h"
#include "failover/proxy_picker.h"
#include "protocol/error_mapping.h"
#include "protocol/request_builder.h"
#include "protocol/response.h"
#include "util/thread_pool.h"
#include "util/url.h"
#include "util/uuid.h"
namespace agw {
namespace {
bool isCancelled(const CancellationToken *cancel)
{
return cancel != nullptr && cancel->isCancelled();
}
std::function<bool()> makeCancelCheck(CancellationToken *cancel)
{
if (cancel == nullptr) {
return {};
}
return [cancel] { return cancel->isCancelled(); };
}
std::string threadIdStr()
{
std::ostringstream oss;
oss << std::this_thread::get_id();
return oss.str();
}
const char *transportErrorName(TransportError e)
{
switch (e) {
case TransportError::None: return "None";
case TransportError::Timeout: return "Timeout";
case TransportError::Canceled: return "Canceled";
case TransportError::OperationNotImplemented: return "OperationNotImplemented";
case TransportError::ConnectionError: return "ConnectionError";
}
return "?";
}
}
struct GatewayController::Impl {
Config config;
std::shared_ptr<IHttpClient> http;
std::unique_ptr<crypto::IRng> rng;
std::mutex proxyMutex;
std::string cachedProxy;
util::ThreadPool pool;
explicit Impl(Config cfg)
: config(std::move(cfg)),
rng(std::make_unique<crypto::DefaultRng>()),
pool(static_cast<std::size_t>(config.threadPoolSize))
{
http = config.httpClient ? config.httpClient
: std::shared_ptr<IHttpClient>(makeDefaultHttpClient());
log(LogLevel::Info,
"client created: dev=" + std::string(config.isDevEnvironment ? "1" : "0")
+ " timeout=" + std::to_string(config.requestTimeoutMsecs) + "ms"
+ " pool=" + std::to_string(config.threadPoolSize)
+ " s3primary=" + std::to_string(config.s3PrimaryEndpoints.size())
+ " s3fallback=" + std::to_string(config.s3FallbackEndpoints.size())
+ " customHttp=" + std::string(config.httpClient ? "1" : "0"));
}
void log(LogLevel level, const std::string &message) const
{
if (config.log) {
config.log(level, message);
}
}
void dbg(const std::string &message) const { log(LogLevel::Debug, message); }
std::string getCachedProxy()
{
std::lock_guard<std::mutex> lock(proxyMutex);
return cachedProxy;
}
void setCachedProxy(const std::string &proxy)
{
std::lock_guard<std::mutex> lock(proxyMutex);
cachedProxy = proxy;
}
bool attempt(const std::string &endpoint, const std::string &host, const HttpRequest &baseReq,
const std::vector<std::uint8_t> &key, const std::vector<std::uint8_t> &iv,
HttpResponse &resp, protocol::DecryptResult &dec)
{
HttpRequest req = baseReq;
req.url = util::formatEndpoint(endpoint, host);
dbg(" proxy attempt: POST " + req.url);
const auto t0 = std::chrono::steady_clock::now();
resp = http->send(req);
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - t0).count();
dec = protocol::tryDecryptResponse(resp.body, key, iv);
const bool bypass = resp.sslError || failover::shouldBypassProxy(resp.error, dec.decryptedBody, dec.ok);
dbg(" proxy attempt result: transport=" + std::string(transportErrorName(resp.error))
+ " ssl=" + std::string(resp.sslError ? "1" : "0") + " http=" + std::to_string(resp.httpStatusCode)
+ " bodyLen=" + std::to_string(resp.body.size()) + " decryptOk=" + std::string(dec.ok ? "1" : "0")
+ " bypassAgain=" + std::string(bypass ? "1" : "0") + " (" + std::to_string(ms) + "ms)");
return !bypass;
}
void runFailover(const std::string &endpoint, const HttpRequest &baseReq, const FailoverContext &ctx,
const std::vector<std::uint8_t> &key, const std::vector<std::uint8_t> &iv,
HttpResponse &resp, protocol::DecryptResult &dec, CancellationToken *cancel)
{
if (isCancelled(cancel)) {
dbg("failover: cancelled before start");
return;
}
std::random_device rd;
std::mt19937 gen(rd());
std::vector<std::string> primary = config.s3PrimaryEndpoints;
std::vector<std::string> fallback = config.s3FallbackEndpoints;
std::shuffle(primary.begin(), primary.end(), gen);
std::shuffle(fallback.begin(), fallback.end(), gen);
const std::vector<std::string> storageUrls = failover::buildStorageUrls(primary, fallback, ctx);
dbg("failover: storage urls=" + std::to_string(storageUrls.size())
+ " service='" + ctx.serviceType + "' country='" + ctx.userCountryCode + "'");
std::vector<std::string> proxyUrls;
for (const auto &storageUrl : storageUrls) {
if (isCancelled(cancel)) {
dbg("failover: cancelled during storage fetch");
return;
}
HttpRequest g;
g.url = storageUrl;
g.method = "GET";
g.headers = {{"Content-Type", "application/json"}};
g.timeoutMsecs = config.proxyStorageTimeoutMsecs;
g.cancelCheck = makeCancelCheck(cancel);
const HttpResponse gr = http->send(g);
dbg(" storage GET " + storageUrl + " → transport=" + std::string(transportErrorName(gr.error))
+ " ssl=" + std::string(gr.sslError ? "1" : "0") + " http=" + std::to_string(gr.httpStatusCode)
+ " bodyLen=" + std::to_string(gr.body.size()));
if (gr.error != TransportError::None || gr.sslError) {
continue;
}
try {
proxyUrls = failover::decodeProxyList(gr.body, config.isDevEnvironment, config.agwPublicKeyPem);
dbg(" decoded proxy list: " + std::to_string(proxyUrls.size()) + " proxies");
break;
} catch (...) {
dbg(" proxy list decode failed → next storage");
continue;
}
}
std::shuffle(proxyUrls.begin(), proxyUrls.end(), gen);
std::string proxy = getCachedProxy();
if (proxy.empty()) {
if (isCancelled(cancel)) {
dbg("failover: cancelled before health-check");
return;
}
dbg("failover: no cached proxy → health-check of " + std::to_string(proxyUrls.size()) + " proxies");
proxy = failover::pickHealthyProxy(*http, proxyUrls, config.proxyHealthTimeoutMsecs);
if (!proxy.empty()) {
dbg("failover: healthy proxy = " + proxy + " (cached)");
setCachedProxy(proxy);
} else {
dbg("failover: no healthy proxy found");
}
} else {
dbg("failover: using cached proxy = " + proxy);
}
if (!proxy.empty()) {
if (isCancelled(cancel)) {
return;
}
if (attempt(endpoint, proxy, baseReq, key, iv, resp, dec)) {
dbg("failover: succeeded via cached/first proxy");
return;
}
}
for (const auto &p : proxyUrls) {
if (isCancelled(cancel)) {
return;
}
if (attempt(endpoint, p, baseReq, key, iv, resp, dec)) {
dbg("failover: succeeded via proxy " + p + " (cached)");
setCachedProxy(p);
return;
}
}
dbg("failover: exhausted all proxies (using last attempt result)");
}
Response executePost(const std::string &endpoint, const std::string &payload,
const FailoverContext &ctx, CancellationToken *cancel)
{
const auto tStart = std::chrono::steady_clock::now();
log(LogLevel::Info, "post START endpoint='" + endpoint + "' service='" + ctx.serviceType
+ "' country='" + ctx.userCountryCode + "' payloadLen=" + std::to_string(payload.size())
+ " thread=" + threadIdStr());
if (isCancelled(cancel)) {
log(LogLevel::Info, "post: cancelled before start");
return Response{ErrorCode::Cancelled, std::string()};
}
protocol::EncryptedRequest enc =
protocol::buildEncryptedRequest(payload, config.agwPublicKeyPem, *rng);
if (enc.error != ErrorCode::NoError) {
log(LogLevel::Warning, "post: request build failed error="
+ std::to_string(static_cast<int>(enc.error)));
return Response{enc.error, std::string()};
}
dbg("request built: bodyLen=" + std::to_string(enc.body.size()) + " (key/iv/salt generated)");
if (isCancelled(cancel)) {
return Response{ErrorCode::Cancelled, std::string()};
}
const std::string requestId = util::makeUuidV4(*rng);
const std::string cached = getCachedProxy();
const std::string directHost = cached.empty() ? config.gatewayEndpoint : cached;
const std::string url = util::formatEndpoint(endpoint, directHost);
dbg("direct request: url=" + url + " reqId=" + requestId
+ " viaCachedProxy=" + std::string(cached.empty() ? "0" : "1"));
if (config.onBeforeRequest) {
const std::string host = util::extractHost(url);
dbg("onBeforeRequest(host=" + host + ")");
config.onBeforeRequest(host);
}
HttpRequest req;
req.url = url;
req.method = "POST";
req.body = enc.body;
req.headers = {
{"Content-Type", "application/json"},
{"X-Client-Request-ID", requestId},
};
req.timeoutMsecs = config.requestTimeoutMsecs;
req.cancelCheck = makeCancelCheck(cancel);
const auto t0 = std::chrono::steady_clock::now();
HttpResponse resp = http->send(req);
const auto httpMs = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - t0).count();
if (isCancelled(cancel)) {
log(LogLevel::Info, "post: cancelled after direct send");
return Response{ErrorCode::Cancelled, std::string()};
}
protocol::DecryptResult dec = protocol::tryDecryptResponse(resp.body, enc.key, enc.iv);
dbg("direct response: transport=" + std::string(transportErrorName(resp.error))
+ " ssl=" + std::string(resp.sslError ? "1" : "0") + " http=" + std::to_string(resp.httpStatusCode)
+ " bodyLen=" + std::to_string(resp.body.size()) + " decryptOk=" + std::string(dec.ok ? "1" : "0")
+ " (" + std::to_string(httpMs) + "ms)");
const bool bypass = !resp.sslError
&& failover::shouldBypassProxy(resp.error, dec.decryptedBody, dec.ok);
if (bypass) {
log(LogLevel::Info, "direct response suspicious — running failover");
runFailover(endpoint, req, ctx, enc.key, enc.iv, resp, dec, cancel);
if (isCancelled(cancel)) {
log(LogLevel::Info, "post: cancelled during failover");
return Response{ErrorCode::Cancelled, std::string()};
}
} else {
dbg("direct response accepted (no failover)");
}
Response out;
out.body = dec.decryptedBody;
const ErrorCode mapped = protocol::mapResponseError(resp.sslError, resp.error, dec.decryptedBody);
const auto totalMs = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - tStart).count();
if (mapped != ErrorCode::NoError) {
out.error = mapped;
log(LogLevel::Warning, "post DONE error=" + std::to_string(static_cast<int>(mapped))
+ " bodyLen=" + std::to_string(out.body.size()) + " (" + std::to_string(totalMs) + "ms)");
return out;
}
if (!dec.ok) {
out.error = ErrorCode::ApiConfigDecryptionError;
log(LogLevel::Error, "post DONE: response decryption failed (1106) ("
+ std::to_string(totalMs) + "ms)");
return out;
}
out.error = ErrorCode::NoError;
log(LogLevel::Info, "post DONE ok bodyLen=" + std::to_string(out.body.size())
+ " (" + std::to_string(totalMs) + "ms)");
return out;
}
};
GatewayController::GatewayController(Config config) : m_impl(std::make_unique<Impl>(std::move(config))) {}
GatewayController::~GatewayController() = default;
GatewayController::GatewayController(GatewayController &&) noexcept = default;
GatewayController &GatewayController::operator=(GatewayController &&) noexcept = default;
Response GatewayController::post(const std::string &endpoint, const std::string &payload,
const FailoverContext &ctx, CancellationToken *cancel)
{
return m_impl->executePost(endpoint, payload, ctx, cancel);
}
void GatewayController::postAsync(const std::string &endpoint, const std::string &payload,
std::function<void(Response)> onResult, const FailoverContext &ctx,
CancellationToken *cancel)
{
Impl *impl = m_impl.get();
impl->dbg("postAsync: submitting to pool (caller thread=" + threadIdStr() + ")");
impl->pool.submit([impl, endpoint, payload, onResult = std::move(onResult), ctx, cancel]() {
impl->dbg("postAsync: running on pool thread=" + threadIdStr());
Response r = impl->executePost(endpoint, payload, ctx, cancel);
if (onResult) {
onResult(std::move(r));
}
});
}
std::future<Response> GatewayController::postFuture(const std::string &endpoint, const std::string &payload,
const FailoverContext &ctx, CancellationToken *cancel)
{
auto promise = std::make_shared<std::promise<Response>>();
std::future<Response> fut = promise->get_future();
Impl *impl = m_impl.get();
impl->dbg("postFuture: submitting to pool (caller thread=" + threadIdStr() + ")");
impl->pool.submit([impl, endpoint, payload, ctx, cancel, promise]() {
impl->dbg("postFuture: running on pool thread=" + threadIdStr());
promise->set_value(impl->executePost(endpoint, payload, ctx, cancel));
});
return fut;
}
}

View File

@@ -0,0 +1,132 @@
#ifdef AGW_HAVE_CURL
#include "http/curl_client.h"
#include <mutex>
#include <string>
#include <curl/curl.h>
namespace agw
{
namespace
{
std::once_flag g_curlInitOnce;
void ensureCurlGlobalInit()
{
std::call_once(g_curlInitOnce, []() { curl_global_init(CURL_GLOBAL_DEFAULT); });
}
std::size_t writeCallback(char *ptr, std::size_t size, std::size_t nmemb, void *userdata)
{
const std::size_t total = size * nmemb;
auto *buf = static_cast<std::string *>(userdata);
buf->append(ptr, total);
return total;
}
int xferCallback(void *clientp, curl_off_t, curl_off_t, curl_off_t, curl_off_t)
{
auto *check = static_cast<const std::function<bool()> *>(clientp);
if (check && *check && (*check)()) {
return 1;
}
return 0;
}
TransportError mapCurlError(CURLcode code, bool &sslError)
{
sslError = false;
switch (code) {
case CURLE_OK: return TransportError::None;
case CURLE_OPERATION_TIMEDOUT: return TransportError::Timeout;
case CURLE_ABORTED_BY_CALLBACK: return TransportError::Canceled;
case CURLE_SSL_CONNECT_ERROR:
case CURLE_PEER_FAILED_VERIFICATION:
case CURLE_SSL_CERTPROBLEM:
case CURLE_SSL_CIPHER:
case CURLE_SSL_CACERT_BADFILE:
case CURLE_SSL_ISSUER_ERROR: sslError = true; return TransportError::ConnectionError;
default: return TransportError::ConnectionError;
}
}
}
CurlHttpClient::CurlHttpClient()
{
ensureCurlGlobalInit();
}
CurlHttpClient::~CurlHttpClient() = default;
HttpResponse CurlHttpClient::send(const HttpRequest &request)
{
HttpResponse response;
CURL *curl = curl_easy_init();
if (!curl) {
response.error = TransportError::ConnectionError;
response.errorString = "curl_easy_init failed";
return response;
}
struct curl_slist *headers = nullptr;
for (const auto &h : request.headers) {
const std::string line = h.first + ": " + h.second;
headers = curl_slist_append(headers, line.c_str());
}
curl_easy_setopt(curl, CURLOPT_URL, request.url.c_str());
if (headers) {
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
}
if (request.timeoutMsecs > 0) {
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, static_cast<long>(request.timeoutMsecs));
}
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response.body);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
if (request.cancelCheck) {
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xferCallback);
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &request.cancelCheck);
}
if (request.method == "POST") {
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request.body.data());
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, static_cast<long>(request.body.size()));
} else {
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
}
const CURLcode code = curl_easy_perform(curl);
bool sslError = false;
response.error = mapCurlError(code, sslError);
response.sslError = sslError;
if (code != CURLE_OK) {
response.errorString = curl_easy_strerror(code);
}
long httpCode = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
response.httpStatusCode = static_cast<int>(httpCode);
if (headers) {
curl_slist_free_all(headers);
}
curl_easy_cleanup(curl);
return response;
}
std::unique_ptr<IHttpClient> makeDefaultHttpClient()
{
return std::make_unique<CurlHttpClient>();
}
}
#endif

View File

@@ -0,0 +1,17 @@
#ifndef AGW_HTTP_CURL_CLIENT_H
#define AGW_HTTP_CURL_CLIENT_H
#include "agw/http.h"
namespace agw
{
class CurlHttpClient : public IHttpClient
{
public:
CurlHttpClient();
~CurlHttpClient() override;
HttpResponse send(const HttpRequest &request) override;
};
}
#endif

View File

@@ -0,0 +1,15 @@
#ifndef AGW_HAVE_CURL
#include <stdexcept>
#include "agw/http.h"
namespace agw
{
std::unique_ptr<IHttpClient> makeDefaultHttpClient()
{
throw std::runtime_error("agw: SDK built without libcurl; provide Config::httpClient (your own IHttpClient)");
}
}
#endif

View File

@@ -0,0 +1,133 @@
#include "protocol/error_mapping.h"
#include <algorithm>
#include <cctype>
#include "protocol/keys.h"
#include "util/json.h"
namespace agw::protocol
{
namespace
{
constexpr int kConflict = 409;
constexpr int kNotFound = 404;
constexpr int kNotImplemented = 501;
constexpr int kPaymentRequired = 402;
constexpr int kTooManyRequests = 429;
constexpr int kRequestTimeout = 408;
constexpr int kUnprocessableEntity = 422;
constexpr const char *kUnprocessableSubscriptionMessage =
"Failed to retrieve subscription information. Is it activated?";
constexpr const char *kTrialAlreadyUsedMessage = "trial subscription already used";
std::string trim(const std::string &s)
{
std::size_t b = 0;
std::size_t e = s.size();
while (b < e && std::isspace(static_cast<unsigned char>(s[b])))
++b;
while (e > b && std::isspace(static_cast<unsigned char>(s[e - 1])))
--e;
return s.substr(b, e - b);
}
std::string toLower(std::string s)
{
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return s;
}
bool containsCI(const std::string &haystack, const std::string &needle)
{
return toLower(haystack).find(toLower(needle)) != std::string::npos;
}
std::string messageFrom(const util::Json &obj)
{
auto it = obj.find(keys::message);
if (it != obj.end() && it->is_string()) {
return trim(it->get<std::string>());
}
return { };
}
}
ErrorCode mapResponseError(bool sslError, TransportError transportError, const std::string &decryptedBody)
{
if (sslError) {
return ErrorCode::ApiConfigSslError;
}
if (transportError == TransportError::Timeout || transportError == TransportError::Canceled) {
return ErrorCode::ApiConfigTimeoutError;
}
if (transportError == TransportError::OperationNotImplemented) {
return ErrorCode::ApiUpdateRequestError;
}
util::Json obj;
bool isObject = false;
try {
obj = util::Json::parse(decryptedBody);
isObject = obj.is_object();
} catch (...) {
isObject = false;
}
if (isObject) {
int httpStatus = -1;
if (auto it = obj.find(keys::httpStatus); it != obj.end() && it->is_number_integer()) {
httpStatus = it->get<int>();
}
const std::string message = messageFrom(obj);
if (httpStatus == kTooManyRequests) {
return ErrorCode::ApiRateLimitError;
}
if (httpStatus == kConflict) {
if (containsCI(message, kTrialAlreadyUsedMessage)) {
return ErrorCode::ApiTrialAlreadyUsedError;
}
return ErrorCode::ApiConfigLimitError;
}
if (httpStatus == kNotFound) {
return ErrorCode::ApiNotFoundError;
}
if (httpStatus == kRequestTimeout) {
return ErrorCode::ApiConfigTimeoutError;
}
if (httpStatus == kNotImplemented) {
return ErrorCode::ApiUpdateRequestError;
}
if (httpStatus == kUnprocessableEntity) {
if (message == kUnprocessableSubscriptionMessage) {
return ErrorCode::ApiSubscriptionExpiredError;
}
return ErrorCode::ApiConfigDownloadError;
}
if (httpStatus == kPaymentRequired) {
if (containsCI(message, "refresh_captcha")) {
return ErrorCode::ApiCaptchaRefreshError;
}
if (containsCI(message, "invalid_captcha")) {
return ErrorCode::ApiCaptchaInvalidError;
}
if (obj.contains("captcha_id") || obj.contains("captcha_image")
|| containsCI(message, "rate_limit_exceeded")) {
return ErrorCode::ApiCaptchaRequiredError;
}
return ErrorCode::ApiSubscriptionNotActiveError;
}
if (httpStatus >= 300) {
return ErrorCode::ApiConfigDownloadError;
}
}
if (transportError == TransportError::None) {
return ErrorCode::NoError;
}
return ErrorCode::ApiConfigDownloadError;
}
}

View File

@@ -0,0 +1,14 @@
#ifndef AGW_PROTOCOL_ERROR_MAPPING_H
#define AGW_PROTOCOL_ERROR_MAPPING_H
#include <string>
#include "agw/http.h"
#include "agw/types.h"
namespace agw::protocol
{
ErrorCode mapResponseError(bool sslError, TransportError transportError, const std::string &decryptedBody);
}
#endif

View File

@@ -0,0 +1,18 @@
#ifndef AGW_PROTOCOL_KEYS_H
#define AGW_PROTOCOL_KEYS_H
namespace agw::protocol::keys {
inline constexpr const char *aesKey = "aes_key";
inline constexpr const char *aesIv = "aes_iv";
inline constexpr const char *aesSalt = "aes_salt";
inline constexpr const char *apiPayload = "api_payload";
inline constexpr const char *keyPayload = "key_payload";
inline constexpr const char *serviceType = "service_type";
inline constexpr const char *userCountryCode = "user_country_code";
inline constexpr const char *httpStatus = "http_status";
inline constexpr const char *message = "message";
}
#endif

View File

@@ -0,0 +1,55 @@
#include "protocol/request_builder.h"
#include "crypto/aes.h"
#include "crypto/rsa.h"
#include "protocol/keys.h"
#include "util/base64.h"
#include "util/json.h"
namespace agw::protocol
{
namespace
{
std::vector<std::uint8_t> bytesOf(const std::string &s)
{
return std::vector<std::uint8_t>(s.begin(), s.end());
}
}
EncryptedRequest buildEncryptedRequest(const std::string &payload, const std::string &publicKeyPem, crypto::IRng &rng)
{
namespace k = keys;
EncryptedRequest out;
out.key = rng.bytes(32);
out.iv = rng.bytes(32);
out.salt = rng.bytes(8);
if (!crypto::rsaPublicKeyValid(publicKeyPem)) {
out.error = ErrorCode::ApiMissingAgwPublicKey;
return out;
}
util::Json keysJson;
keysJson[k::aesKey] = util::base64Encode(out.key);
keysJson[k::aesIv] = util::base64Encode(out.iv);
keysJson[k::aesSalt] = util::base64Encode(out.salt);
const std::string keysSerialized = util::qtIndentedDump(keysJson);
std::string keyPayloadB64;
std::string apiPayloadB64;
try {
keyPayloadB64 = util::base64Encode(crypto::rsaEncryptPublicPkcs1(bytesOf(keysSerialized), publicKeyPem));
apiPayloadB64 = util::base64Encode(crypto::aesEncryptCbc(bytesOf(payload), out.key, out.iv));
} catch (...) {
out.error = ErrorCode::ApiConfigDecryptionError;
return out;
}
util::Json body;
body[k::keyPayload] = keyPayloadB64;
body[k::apiPayload] = apiPayloadB64;
out.body = util::qtIndentedDump(body);
return out;
}
}

View File

@@ -0,0 +1,26 @@
#ifndef AGW_PROTOCOL_REQUEST_BUILDER_H
#define AGW_PROTOCOL_REQUEST_BUILDER_H
#include <cstdint>
#include <string>
#include <vector>
#include "agw/types.h"
#include "crypto/rng.h"
namespace agw::protocol
{
struct EncryptedRequest
{
std::string body;
std::vector<std::uint8_t> key;
std::vector<std::uint8_t> iv;
std::vector<std::uint8_t> salt;
ErrorCode error = ErrorCode::NoError;
};
EncryptedRequest buildEncryptedRequest(const std::string &payload, const std::string &publicKeyPem,
crypto::IRng &rng);
}
#endif

View File

@@ -0,0 +1,24 @@
#include "protocol/response.h"
#include "crypto/aes.h"
namespace agw::protocol
{
DecryptResult tryDecryptResponse(const std::string &encrypted, const std::vector<std::uint8_t> &key,
const std::vector<std::uint8_t> &iv)
{
DecryptResult result;
result.decryptedBody = encrypted;
result.ok = false;
try {
const std::vector<std::uint8_t> in(encrypted.begin(), encrypted.end());
const std::vector<std::uint8_t> out = crypto::aesDecryptCbc(in, key, iv);
result.decryptedBody.assign(out.begin(), out.end());
result.ok = true;
} catch (...) {
result.decryptedBody = encrypted;
result.ok = false;
}
return result;
}
}

View File

@@ -0,0 +1,20 @@
#ifndef AGW_PROTOCOL_RESPONSE_H
#define AGW_PROTOCOL_RESPONSE_H
#include <cstdint>
#include <string>
#include <vector>
namespace agw::protocol
{
struct DecryptResult
{
std::string decryptedBody;
bool ok = false;
};
DecryptResult tryDecryptResponse(const std::string &encrypted, const std::vector<std::uint8_t> &key,
const std::vector<std::uint8_t> &iv);
}
#endif

108
agw-sdk/src/util/base64.cpp Normal file
View File

@@ -0,0 +1,108 @@
#include "base64.h"
#include <array>
namespace agw::util
{
namespace
{
const char *kStd = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const char *kUrl = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
std::string encode(const std::vector<std::uint8_t> &data, const char *alphabet, bool pad)
{
std::string out;
out.reserve((data.size() + 2) / 3 * 4);
std::size_t i = 0;
const std::size_t n = data.size();
while (i + 3 <= n) {
const std::uint32_t v = (std::uint32_t(data[i]) << 16) | (std::uint32_t(data[i + 1]) << 8) | data[i + 2];
out.push_back(alphabet[(v >> 18) & 0x3F]);
out.push_back(alphabet[(v >> 12) & 0x3F]);
out.push_back(alphabet[(v >> 6) & 0x3F]);
out.push_back(alphabet[v & 0x3F]);
i += 3;
}
const std::size_t rem = n - i;
if (rem == 1) {
const std::uint32_t v = std::uint32_t(data[i]) << 16;
out.push_back(alphabet[(v >> 18) & 0x3F]);
out.push_back(alphabet[(v >> 12) & 0x3F]);
if (pad) {
out.push_back('=');
out.push_back('=');
}
} else if (rem == 2) {
const std::uint32_t v = (std::uint32_t(data[i]) << 16) | (std::uint32_t(data[i + 1]) << 8);
out.push_back(alphabet[(v >> 18) & 0x3F]);
out.push_back(alphabet[(v >> 12) & 0x3F]);
out.push_back(alphabet[(v >> 6) & 0x3F]);
if (pad) {
out.push_back('=');
}
}
return out;
}
int decodeChar(char c)
{
if (c >= 'A' && c <= 'Z')
return c - 'A';
if (c >= 'a' && c <= 'z')
return c - 'a' + 26;
if (c >= '0' && c <= '9')
return c - '0' + 52;
if (c == '+' || c == '-')
return 62;
if (c == '/' || c == '_')
return 63;
return -1;
}
}
std::string base64Encode(const std::vector<std::uint8_t> &data)
{
return encode(data, kStd, true);
}
std::string base64UrlEncodeNoPad(const std::vector<std::uint8_t> &data)
{
return encode(data, kUrl, false);
}
std::string base64Encode(const std::string &data)
{
return base64Encode(std::vector<std::uint8_t>(data.begin(), data.end()));
}
std::vector<std::uint8_t> base64Decode(const std::string &text)
{
std::vector<std::uint8_t> out;
out.reserve(text.size() / 4 * 3 + 3);
std::array<int, 4> quad { };
int count = 0;
for (char c : text) {
const int v = decodeChar(c);
if (v < 0) {
continue;
}
quad[count++] = v;
if (count == 4) {
out.push_back(static_cast<std::uint8_t>((quad[0] << 2) | (quad[1] >> 4)));
out.push_back(static_cast<std::uint8_t>((quad[1] << 4) | (quad[2] >> 2)));
out.push_back(static_cast<std::uint8_t>((quad[2] << 6) | quad[3]));
count = 0;
}
}
if (count == 2) {
out.push_back(static_cast<std::uint8_t>((quad[0] << 2) | (quad[1] >> 4)));
} else if (count == 3) {
out.push_back(static_cast<std::uint8_t>((quad[0] << 2) | (quad[1] >> 4)));
out.push_back(static_cast<std::uint8_t>((quad[1] << 4) | (quad[2] >> 2)));
}
return out;
}
}

19
agw-sdk/src/util/base64.h Normal file
View File

@@ -0,0 +1,19 @@
#ifndef AGW_UTIL_BASE64_H
#define AGW_UTIL_BASE64_H
#include <cstdint>
#include <string>
#include <vector>
namespace agw::util
{
std::string base64Encode(const std::vector<std::uint8_t> &data);
std::string base64UrlEncodeNoPad(const std::vector<std::uint8_t> &data);
std::vector<std::uint8_t> base64Decode(const std::string &text);
std::string base64Encode(const std::string &data);
}
#endif

108
agw-sdk/src/util/json.cpp Normal file
View File

@@ -0,0 +1,108 @@
#include "json.h"
#include <cstdint>
namespace agw::util
{
namespace
{
const char *kHex = "0123456789abcdef";
void appendEscaped(std::string &out, const std::string &s)
{
out.push_back('"');
for (unsigned char c : s) {
switch (c) {
case '"': out += "\\\""; break;
case '\\': out += "\\\\"; break;
case '\b': out += "\\b"; break;
case '\f': out += "\\f"; break;
case '\n': out += "\\n"; break;
case '\r': out += "\\r"; break;
case '\t': out += "\\t"; break;
default:
if (c < 0x20) {
out += "\\u00";
out.push_back(kHex[c >> 4]);
out.push_back(kHex[c & 0x0F]);
} else {
out.push_back(static_cast<char>(c));
}
}
}
out.push_back('"');
}
void appendIndent(std::string &out, int level)
{
out.append(static_cast<std::size_t>(level) * 4, ' ');
}
void dumpValue(std::string &out, const Json &j, int indent);
void dumpObject(std::string &out, const Json &j, int indent)
{
out += "{\n";
const int inner = indent + 1;
std::size_t i = 0;
const std::size_t n = j.size();
for (auto it = j.begin(); it != j.end(); ++it, ++i) {
appendIndent(out, inner);
appendEscaped(out, it.key());
out += ": ";
dumpValue(out, it.value(), inner);
if (i + 1 < n) {
out.push_back(',');
}
out.push_back('\n');
}
appendIndent(out, indent);
out.push_back('}');
}
void dumpArray(std::string &out, const Json &j, int indent)
{
out += "[\n";
const int inner = indent + 1;
std::size_t i = 0;
const std::size_t n = j.size();
for (const auto &el : j) {
appendIndent(out, inner);
dumpValue(out, el, inner);
if (i + 1 < n) {
out.push_back(',');
}
out.push_back('\n');
++i;
}
appendIndent(out, indent);
out.push_back(']');
}
void dumpValue(std::string &out, const Json &j, int indent)
{
switch (j.type()) {
case Json::value_t::object: dumpObject(out, j, indent); break;
case Json::value_t::array: dumpArray(out, j, indent); break;
case Json::value_t::string: appendEscaped(out, j.get<std::string>()); break;
case Json::value_t::boolean: out += j.get<bool>() ? "true" : "false"; break;
case Json::value_t::null: out += "null"; break;
case Json::value_t::number_integer:
case Json::value_t::number_unsigned:
case Json::value_t::number_float:
default:
out += j.dump();
break;
}
}
}
std::string qtIndentedDump(const Json &j)
{
std::string out;
dumpValue(out, j, 0);
out.push_back('\n');
return out;
}
}

15
agw-sdk/src/util/json.h Normal file
View File

@@ -0,0 +1,15 @@
#ifndef AGW_UTIL_JSON_H
#define AGW_UTIL_JSON_H
#include <string>
#include <nlohmann/json.hpp>
namespace agw::util
{
using Json = nlohmann::json;
std::string qtIndentedDump(const Json &j);
}
#endif

View File

@@ -0,0 +1,59 @@
#include "util/thread_pool.h"
namespace agw::util
{
ThreadPool::ThreadPool(std::size_t threadCount)
{
if (threadCount == 0) {
threadCount = 1;
}
m_workers.reserve(threadCount);
for (std::size_t i = 0; i < threadCount; ++i) {
m_workers.emplace_back([this] { workerLoop(); });
}
}
ThreadPool::~ThreadPool()
{
{
std::lock_guard<std::mutex> lock(m_mutex);
m_stopping = true;
}
m_cv.notify_all();
for (auto &w : m_workers) {
if (w.joinable()) {
w.join();
}
}
}
void ThreadPool::submit(std::function<void()> task)
{
{
std::lock_guard<std::mutex> lock(m_mutex);
m_tasks.push(std::move(task));
}
m_cv.notify_one();
}
void ThreadPool::workerLoop()
{
for (;;) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(m_mutex);
m_cv.wait(lock, [this] { return m_stopping || !m_tasks.empty(); });
if (m_tasks.empty()) {
if (m_stopping) {
return;
}
continue;
}
task = std::move(m_tasks.front());
m_tasks.pop();
}
task();
}
}
}

View File

@@ -0,0 +1,36 @@
#ifndef AGW_UTIL_THREAD_POOL_H
#define AGW_UTIL_THREAD_POOL_H
#include <condition_variable>
#include <cstddef>
#include <functional>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>
namespace agw::util
{
class ThreadPool
{
public:
explicit ThreadPool(std::size_t threadCount);
~ThreadPool();
ThreadPool(const ThreadPool &) = delete;
ThreadPool &operator=(const ThreadPool &) = delete;
void submit(std::function<void()> task);
private:
void workerLoop();
std::vector<std::thread> m_workers;
std::queue<std::function<void()>> m_tasks;
std::mutex m_mutex;
std::condition_variable m_cv;
bool m_stopping = false;
};
}
#endif

48
agw-sdk/src/util/url.cpp Normal file
View File

@@ -0,0 +1,48 @@
#include "util/url.h"
namespace agw::util
{
std::string formatEndpoint(const std::string &endpoint, const std::string &host)
{
const std::string token = "%1";
const std::size_t pos = endpoint.find(token);
if (pos == std::string::npos) {
return endpoint;
}
std::string out = endpoint;
out.replace(pos, token.size(), host);
return out;
}
std::string extractHost(const std::string &url)
{
std::size_t start = 0;
const std::size_t scheme = url.find("://");
if (scheme != std::string::npos) {
start = scheme + 3;
}
std::size_t end = url.size();
for (std::size_t i = start; i < url.size(); ++i) {
const char c = url[i];
if (c == '/' || c == '?' || c == '#') {
end = i;
break;
}
}
std::string authority = url.substr(start, end - start);
const std::size_t at = authority.find('@');
if (at != std::string::npos) {
authority = authority.substr(at + 1);
}
const std::size_t colon = authority.find(':');
if (colon != std::string::npos) {
authority = authority.substr(0, colon);
}
return authority;
}
}

13
agw-sdk/src/util/url.h Normal file
View File

@@ -0,0 +1,13 @@
#ifndef AGW_UTIL_URL_H
#define AGW_UTIL_URL_H
#include <string>
namespace agw::util
{
std::string formatEndpoint(const std::string &endpoint, const std::string &host);
std::string extractHost(const std::string &url);
}
#endif

36
agw-sdk/src/util/uuid.cpp Normal file
View File

@@ -0,0 +1,36 @@
#include "uuid.h"
#include <cstdint>
#include <vector>
namespace agw::util
{
namespace
{
const char *kHex = "0123456789abcdef";
void appendHex(std::string &out, std::uint8_t b)
{
out.push_back(kHex[b >> 4]);
out.push_back(kHex[b & 0x0F]);
}
}
std::string makeUuidV4(crypto::IRng &rng)
{
std::vector<std::uint8_t> b = rng.bytes(16);
b[6] = static_cast<std::uint8_t>((b[6] & 0x0F) | 0x40);
b[8] = static_cast<std::uint8_t>((b[8] & 0x3F) | 0x80);
std::string out;
out.reserve(36);
for (int i = 0; i < 16; ++i) {
if (i == 4 || i == 6 || i == 8 || i == 10) {
out.push_back('-');
}
appendHex(out, b[i]);
}
return out;
}
}

13
agw-sdk/src/util/uuid.h Normal file
View File

@@ -0,0 +1,13 @@
#ifndef AGW_UTIL_UUID_H
#define AGW_UTIL_UUID_H
#include <string>
#include "crypto/rng.h"
namespace agw::util
{
std::string makeUuidV4(crypto::IRng &rng);
}
#endif

View File

@@ -0,0 +1,27 @@
# Тесты agw-sdk. Локально — на встроенном harness (agw_test.h), без внешнего фреймворка.
set(AGW_FIXTURES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/golden/fixtures")
function(agw_add_test name src)
add_executable(${name} ${src})
target_include_directories(${name} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/src
)
target_link_libraries(${name} PRIVATE agw OpenSSL::Crypto nlohmann_json::nlohmann_json Threads::Threads)
target_compile_definitions(${name} PRIVATE AGW_FIXTURES_DIR="${AGW_FIXTURES_DIR}")
add_test(NAME ${name} COMMAND ${name})
endfunction()
agw_add_test(test_crypto unit/test_crypto.cpp)
agw_add_test(test_json unit/test_json.cpp)
agw_add_test(test_golden golden/test_golden.cpp)
agw_add_test(test_error_mapping unit/test_error_mapping.cpp)
agw_add_test(test_bypass_policy unit/test_bypass_policy.cpp)
agw_add_test(test_proxy_list unit/test_proxy_list.cpp)
agw_add_test(test_thread_pool unit/test_thread_pool.cpp)
agw_add_test(test_post integration/test_post.cpp)
agw_add_test(test_failover integration/test_failover.cpp)
agw_add_test(test_async integration/test_async.cpp)
agw_add_test(test_c_abi integration/test_c_abi.cpp)

39
agw-sdk/tests/agw_test.h Normal file
View File

@@ -0,0 +1,39 @@
#ifndef AGW_TEST_H
#define AGW_TEST_H
#include <cstdio>
#include <cstdlib>
#include <string>
namespace agw_test {
inline int &failCount()
{
static int n = 0;
return n;
}
inline void report(bool ok, const char *expr, const char *file, int line)
{
if (!ok) {
std::fprintf(stderr, "FAIL: %s\n at %s:%d\n", expr, file, line);
++failCount();
}
}
inline void reportEq(const std::string &a, const std::string &b, const char *expr, const char *file, int line)
{
if (a != b) {
std::fprintf(stderr, "FAIL: %s\n at %s:%d\n lhs=[%s]\n rhs=[%s]\n",
expr, file, line, a.c_str(), b.c_str());
++failCount();
}
}
}
#define CHECK(expr) ::agw_test::report((expr), #expr, __FILE__, __LINE__)
#define CHECK_EQ(a, b) ::agw_test::reportEq((a), (b), #a " == " #b, __FILE__, __LINE__)
#define AGW_TEST_MAIN_RETURN() \
(::agw_test::failCount() == 0 ? (std::printf("OK\n"), 0) : (std::fprintf(stderr, "%d check(s) failed\n", ::agw_test::failCount()), 1))
#endif

View File

@@ -0,0 +1 @@
ыaГцЖ7И~▐>ъl╛▌Ц╗ЦaJ╩┤ъ%фTхO╒╢^

View File

@@ -0,0 +1 @@
{"hello":"world"}

View File

@@ -0,0 +1,4 @@
AES_KEY_HEX=000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f
AES_IV_HEX=101112131415161718191a1b1c1d1e1f
AES_PLAINTEXT={"hello":"world"}
AES_CIPHER_B64=2WHnAcP2N+l+jz7fbKyO46jjYUq7h98lxlTIT6K0Xg8=

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDL3pxE7uI3RacQ
jvyFz5tbkL87aqpFBvAVFS0OFzVLbApaJ6nv2jZNfidbPCN5SMeNoa2kNTC+MZNQ
qORgr77TgaRuFap5dSun9qci0ll5Y/zBQHb8/Xihah8YpkbO/8SV1aFWLtWKQiQL
xrFTD9ShXC7S6IQrdGcngUhLShinWmjveZJ//B7no3wUP3xPF+EkTXTq5QyD/SfQ
/w0BUosy55sCn5OyP6iSJ4cqujA7WCEd48XpS5zgceqdnE98mvjhriMDfORXsSnx
ymsDnPyX/wkrLOynpN5KPoCMwXS0knTSGADUPMm/EQXa5fjEDxg0OnJQcJV/mZJk
4Rgi+gZJAgMBAAECggEAXWW2ob3u1POL/gIDninmOqStd0L+jnEHPCFfar0nJU5x
z6usJr4JcqcA0MNUXRQCl9gh/MCBfCCqJKG7PrBE9BDIi8ZROyN6xJAzMbi8VOiB
uucVnAFjak97v4ctmVeDcEFWkG0UVyrF6L82LZ9rAiGBMg5jvqStPWP1AskHUmM+
drJZYsrvqqqZLVbB34I6logBcD0IKEib8uBM3brLrO1t86XpLvOIJEjCsD+GRtmJ
vnjjVcIqHFM+VyA+RvpgMnfbTcG3D4YZhDtdgsbnOHs4mydM/I6C7a5pLftKsdoC
lKgb6CIqJoXvW2goljHfQiBre56hwhBjRgzmdo+BoQKBgQD+aM+P1+nAsIUxWmXf
jexL8LIQ+POxNztz/d9EIYLXOztiS3epwt8e/Xqffu4B5+D1j6u/gtnK90SNcMi2
IhetBwkTGdz6s/JHTt92f7okRbSnzwZwj03Ppkgg3VAPp3LL4nP/a1kcHi+9aqBB
kPPkQ0k9BS/JOmEVPOKpOx0ABwKBgQDNJOjDyhKesjjLK33xJdD6sdhwxFJrSMs3
TtMd0KvPu26Z+gn+5ybE/rzOoO7YZIUmB8AXvNKLu8U/5FRE6eOeaumz2LvFNCzE
IC6J2Oixt/lVxxuR0mSRTJI/hR0CtrofXRke8YjeU3VjtkMW6k2QrPAAMMTPieRW
fkfb8oWTLwKBgQCt0B/W77XFLxSgplkphgYlz/loPR4JOmoFEjLCkn6Y29/zhQnp
UrkrrBRl+ctUQ/7e5lx5yEVSNOOCGscWIG66iS76/NWL9vsVGt7zT8p106XcbEXD
CzUnJDztLybuuwFkKIAFxmqoGjuVls6MXSM0FYBpDy0Ztyfy4Zkd88QZawKBgHB8
rKWvSEZ8s2e0kXqJoe3VVzl+bTMm10eckWbn5U4jGKKV2KVNWpTqmd0zocRGWjxg
Q5TAlTLJ438FVK/1EDrtpPhY/51C3sksXFh5+B57It1GMHflRf/mXMs30pCKYcSQ
6BVvm/1NBjGG34LRN3b9XRy9oS2sDujelcilU1lBAoGBAMHx0oFSlNUHGofigo3p
26kWJw7BE/o8HVIrfI2vekXOgTVgJWGiubjp3qUKgNf1lbRy/Ur9F4ElW7hEINjC
aMJUZWc/xYwAmKHe/7ITZByfRXdGMwlvo8QudbzDC6vvCqn7wQW56kyTTEEHF54k
21jK19EBX4JWibzJotv8ShkU
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy96cRO7iN0WnEI78hc+b
W5C/O2qqRQbwFRUtDhc1S2wKWiep79o2TX4nWzwjeUjHjaGtpDUwvjGTUKjkYK++
04GkbhWqeXUrp/anItJZeWP8wUB2/P14oWofGKZGzv/EldWhVi7VikIkC8axUw/U
oVwu0uiEK3RnJ4FIS0oYp1po73mSf/we56N8FD98TxfhJE106uUMg/0n0P8NAVKL
MuebAp+Tsj+okieHKrowO1ghHePF6Uuc4HHqnZxPfJr44a4jA3zkV7Ep8cprA5z8
l/8JKyzsp6TeSj6AjMF0tJJ00hgA1DzJvxEF2uX4xA8YNDpyUHCVf5mSZOEYIvoG
SQIDAQAB
-----END PUBLIC KEY-----

View File

@@ -0,0 +1,97 @@
#include "agw_test.h"
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include "crypto/aes.h"
#include "crypto/hash.h"
#include "crypto/rsa.h"
#include "protocol/keys.h"
#include "util/base64.h"
#include "util/json.h"
using namespace agw;
namespace {
std::vector<std::uint8_t> bytesOf(const std::string &s)
{
return std::vector<std::uint8_t>(s.begin(), s.end());
}
std::string toStr(const std::vector<std::uint8_t> &v)
{
return std::string(v.begin(), v.end());
}
std::string readFile(const std::string &path)
{
std::ifstream f(path, std::ios::binary);
std::ostringstream ss;
ss << f.rdbuf();
return ss.str();
}
}
int main()
{
namespace k = protocol::keys;
const auto key = crypto::fromHex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
const auto iv = crypto::fromHex("101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f");
const auto salt = crypto::fromHex("a0a1a2a3a4a5a6a7");
const std::string payload = "{\"hello\":\"world\"}";
util::Json keysJson;
keysJson[k::aesKey] = util::base64Encode(key);
keysJson[k::aesIv] = util::base64Encode(iv);
keysJson[k::aesSalt] = util::base64Encode(salt);
const std::string keysSerialized = util::qtIndentedDump(keysJson);
const std::string expectedKeysJson =
"{\n"
" \"aes_iv\": \"EBESExQVFhcYGRobHB0eHyAhIiMkJSYnKCkqKywtLi8=\",\n"
" \"aes_key\": \"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=\",\n"
" \"aes_salt\": \"oKGio6Slpqc=\"\n"
"}\n";
CHECK_EQ(keysSerialized, expectedKeysJson);
const auto apiCipher = crypto::aesEncryptCbc(bytesOf(payload), key, iv);
const std::string apiPayloadB64 = util::base64Encode(apiCipher);
CHECK_EQ(apiPayloadB64, std::string("2WHnAcP2N+l+jz7fbKyO46jjYUq7h98lxlTIT6K0Xg8="));
const std::string pub = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_pub.pem");
const std::string priv = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_priv.pem");
CHECK(!pub.empty());
CHECK(!priv.empty());
const auto keyCipher = crypto::rsaEncryptPublicPkcs1(bytesOf(keysSerialized), pub);
const std::string keyPayloadB64 = util::base64Encode(keyCipher);
const auto keyCipherBack = util::base64Decode(keyPayloadB64);
const auto recovered = crypto::rsaDecryptPrivatePkcs1(keyCipherBack, priv);
CHECK_EQ(toStr(recovered), keysSerialized);
util::Json body;
body[k::keyPayload] = keyPayloadB64;
body[k::apiPayload] = apiPayloadB64;
const std::string bodySerialized = util::qtIndentedDump(body);
util::Json parsed = util::Json::parse(bodySerialized);
CHECK_EQ(parsed[k::apiPayload].get<std::string>(), apiPayloadB64);
{
const auto cBack = util::base64Decode(parsed[k::keyPayload].get<std::string>());
const auto rec = crypto::rsaDecryptPrivatePkcs1(cBack, priv);
CHECK_EQ(toStr(rec), keysSerialized);
}
{
const auto respPlain = bytesOf("{\"ok\":true}");
const auto respCipher = crypto::aesEncryptCbc(respPlain, key, iv);
const auto back = crypto::aesDecryptCbc(respCipher, key, iv);
CHECK(back == respPlain);
}
return AGW_TEST_MAIN_RETURN();
}

View File

@@ -0,0 +1,174 @@
#include "agw_test.h"
#include <atomic>
#include <chrono>
#include <fstream>
#include <future>
#include <memory>
#include <sstream>
#include <string>
#include <thread>
#include <vector>
#include "agw/cancellation.h"
#include "agw/gateway_controller.h"
#include "agw/config.h"
#include "crypto/aes.h"
#include "crypto/rsa.h"
#include "mock_gateway/mock_gateway.h"
#include "protocol/keys.h"
#include "util/base64.h"
#include "util/json.h"
using namespace agw;
namespace {
std::string readFile(const std::string &path)
{
std::ifstream f(path, std::ios::binary);
std::ostringstream ss;
ss << f.rdbuf();
return ss.str();
}
Config baseConfig(std::shared_ptr<IHttpClient> http, const std::string &pub)
{
Config c;
c.gatewayEndpoint = "gw.example.test";
c.agwPublicKeyPem = pub;
c.requestTimeoutMsecs = 5000;
c.httpClient = std::move(http);
return c;
}
class BlockingUntilCancelMock : public IHttpClient {
public:
std::atomic<int> entered{0};
HttpResponse send(const HttpRequest &req) override
{
entered.fetch_add(1);
while (!(req.cancelCheck && req.cancelCheck())) {
std::this_thread::sleep_for(std::chrono::milliseconds(2));
}
HttpResponse r;
r.error = TransportError::Canceled;
return r;
}
};
class StatelessMock : public IHttpClient {
public:
explicit StatelessMock(std::string priv) : m_priv(std::move(priv)) {}
std::atomic<int> count{0};
HttpResponse send(const HttpRequest &req) override
{
count.fetch_add(1);
namespace k = protocol::keys;
util::Json body = util::Json::parse(req.body);
const auto keyCipher = util::base64Decode(body[k::keyPayload].get<std::string>());
const auto keysBytes = crypto::rsaDecryptPrivatePkcs1(keyCipher, m_priv);
util::Json keysJson = util::Json::parse(std::string(keysBytes.begin(), keysBytes.end()));
const auto aesKey = util::base64Decode(keysJson[k::aesKey].get<std::string>());
const auto aesIv = util::base64Decode(keysJson[k::aesIv].get<std::string>());
const std::string plain = R"({"ok":true})";
const std::vector<std::uint8_t> pv(plain.begin(), plain.end());
const auto cipher = crypto::aesEncryptCbc(pv, aesKey, aesIv);
HttpResponse r;
r.httpStatusCode = 200;
r.body.assign(cipher.begin(), cipher.end());
return r;
}
private:
std::string m_priv;
};
}
int main()
{
const std::string pub = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_pub.pem");
const std::string priv = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_priv.pem");
const std::string endpoint = "https://%1/api/v1/test";
const FailoverContext ctx{"prem", "US"};
const std::string payload = R"({"hello":"world"})";
{
auto mock = std::make_shared<agw_test::MockGateway>(priv);
mock->responsePlain = R"({"ok":true,"v":1})";
GatewayController client(baseConfig(mock, pub));
std::future<Response> f = client.postFuture(endpoint, payload, ctx);
Response r = f.get();
CHECK(r.error == ErrorCode::NoError);
CHECK_EQ(r.body, std::string(R"({"ok":true,"v":1})"));
CHECK_EQ(mock->lastDecryptedPayload, payload);
}
{
auto mock = std::make_shared<agw_test::MockGateway>(priv);
mock->responsePlain = R"({"async":true})";
GatewayController client(baseConfig(mock, pub));
std::promise<Response> p;
std::future<Response> f = p.get_future();
client.postAsync(
endpoint, payload, [&p](Response r) { p.set_value(std::move(r)); }, ctx);
Response r = f.get();
CHECK(r.error == ErrorCode::NoError);
CHECK_EQ(r.body, std::string(R"({"async":true})"));
}
{
auto mock = std::make_shared<agw_test::MockGateway>(priv);
GatewayController client(baseConfig(mock, pub));
CancellationToken token;
token.cancel();
std::future<Response> f = client.postFuture(endpoint, payload, ctx, &token);
Response r = f.get();
CHECK(r.error == ErrorCode::Cancelled);
CHECK(mock->requestCount == 0);
}
{
auto mock = std::make_shared<BlockingUntilCancelMock>();
GatewayController client(baseConfig(mock, pub));
CancellationToken token;
std::promise<Response> p;
std::future<Response> f = p.get_future();
client.postAsync(
endpoint, payload, [&p](Response r) { p.set_value(std::move(r)); }, ctx, &token);
while (mock->entered.load() == 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(2));
}
token.cancel();
Response r = f.get();
CHECK(r.error == ErrorCode::Cancelled);
}
{
auto mock = std::make_shared<StatelessMock>(priv);
Config cfg = baseConfig(mock, pub);
cfg.threadPoolSize = 8;
GatewayController client(std::move(cfg));
constexpr int N = 64;
std::vector<std::future<Response>> futs;
futs.reserve(N);
for (int i = 0; i < N; ++i) {
futs.push_back(client.postFuture(endpoint, payload, ctx));
}
int ok = 0;
for (auto &fut : futs) {
Response r = fut.get();
if (r.error == ErrorCode::NoError && r.body == R"({"ok":true})") {
++ok;
}
}
CHECK(ok == N);
CHECK(mock->count.load() == N);
}
return AGW_TEST_MAIN_RETURN();
}

View File

@@ -0,0 +1,118 @@
#include "agw_test.h"
#include <fstream>
#include <future>
#include <memory>
#include <sstream>
#include <string>
#include <utility>
#include "agw/c_abi.h"
#include "agw/types.h"
#include "detail/test_hooks.h"
#include "mock_gateway/mock_gateway.h"
namespace {
std::string readFile(const std::string &path)
{
std::ifstream f(path, std::ios::binary);
std::ostringstream ss;
ss << f.rdbuf();
return ss.str();
}
agw_config makeConfig(const char *gateway, const char *pem)
{
agw_config c{};
c.gateway_endpoint = gateway;
c.agw_public_key_pem = pem;
c.request_timeout_msecs = 5000;
return c;
}
struct AsyncSink {
std::promise<std::pair<int, std::string>> promise;
};
void asyncCallback(agw_response r, void *ud)
{
auto *sink = static_cast<AsyncSink *>(ud);
sink->promise.set_value({r.error, r.body ? std::string(r.body, r.body_len) : std::string()});
agw_response_free(&r);
}
}
int main()
{
const std::string pub = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_pub.pem");
const std::string priv = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_priv.pem");
const std::string payload = R"({"hello":"world"})";
{
auto mock = std::make_shared<agw_test::MockGateway>(priv);
mock->responsePlain = R"({"ok":true,"c":1})";
agw::detail::setNextTestHttpClient(mock);
agw_config cfg = makeConfig("gw.example.test", pub.c_str());
agw_client *client = agw_client_create(&cfg);
CHECK(client != nullptr);
agw_response r = agw_client_post(client, "https://%1/api/v1/test", payload.c_str(), "prem", "US", nullptr);
CHECK(r.error == 0);
CHECK(r.body != nullptr);
CHECK_EQ(std::string(r.body, r.body_len), std::string(R"({"ok":true,"c":1})"));
CHECK_EQ(mock->lastDecryptedPayload, payload);
agw_response_free(&r);
CHECK(r.body == nullptr);
mock->responsePlain = R"({"async":1})";
AsyncSink sink;
auto fut = sink.promise.get_future();
agw_client_post_async(client, "https://%1/api/v1/test", payload.c_str(), "prem", "US",
&asyncCallback, &sink, nullptr);
auto [err, body] = fut.get();
CHECK(err == 0);
CHECK_EQ(body, std::string(R"({"async":1})"));
agw_client_destroy(client);
}
{
auto mock = std::make_shared<agw_test::MockGateway>(priv);
agw::detail::setNextTestHttpClient(mock);
agw_config cfg = makeConfig("gw.example.test", pub.c_str());
agw_client *client = agw_client_create(&cfg);
agw_cancel_token *token = agw_cancel_token_create();
CHECK(token != nullptr);
agw_cancel_token_cancel(token);
agw_response r = agw_client_post(client, "https://%1/api/v1/test", payload.c_str(), "", "", token);
CHECK(r.error == static_cast<int>(agw::ErrorCode::Cancelled));
CHECK(mock->requestCount == 0);
agw_response_free(&r);
agw_cancel_token_destroy(token);
agw_client_destroy(client);
}
{
agw_config cfg = makeConfig("gw.example.test", "not a pem");
agw_client *client = agw_client_create(&cfg);
CHECK(client != nullptr);
agw_response r = agw_client_post(client, "https://%1/x", payload.c_str(), "", "", nullptr);
CHECK(r.error == static_cast<int>(agw::ErrorCode::ApiMissingAgwPublicKey));
agw_response_free(&r);
agw_client_destroy(client);
}
{
CHECK(agw_client_create(nullptr) == nullptr);
agw_response r = agw_client_post(nullptr, "e", "p", "", "", nullptr);
CHECK(r.error != 0);
agw_response_free(&r);
agw_client_destroy(nullptr);
}
return AGW_TEST_MAIN_RETURN();
}

View File

@@ -0,0 +1,125 @@
#include "agw_test.h"
#include <fstream>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include "agw/gateway_controller.h"
#include "agw/config.h"
#include "crypto/aes.h"
#include "crypto/rsa.h"
#include "protocol/keys.h"
#include "util/base64.h"
#include "util/json.h"
using namespace agw;
namespace {
std::string readFile(const std::string &path)
{
std::ifstream f(path, std::ios::binary);
std::ostringstream ss;
ss << f.rdbuf();
return ss.str();
}
bool contains(const std::string &h, const std::string &n) { return h.find(n) != std::string::npos; }
class FailoverMock : public IHttpClient {
public:
explicit FailoverMock(std::string priv) : m_priv(std::move(priv)) {}
int directPosts = 0, proxyPosts = 0, storageGets = 0, healthGets = 0;
HttpResponse send(const HttpRequest &req) override
{
HttpResponse resp;
resp.httpStatusCode = 200;
if (req.method == "GET") {
if (contains(req.url, "lmbd-health")) {
++healthGets;
if (!contains(req.url, "proxy.good.test")) {
resp.error = TransportError::ConnectionError;
}
return resp;
}
++storageGets;
resp.body = R"(["https://proxy.good.test/"])";
return resp;
}
namespace k = protocol::keys;
util::Json body = util::Json::parse(req.body);
const auto keyCipher = util::base64Decode(body[k::keyPayload].get<std::string>());
const auto keysBytes = crypto::rsaDecryptPrivatePkcs1(keyCipher, m_priv);
util::Json keysJson = util::Json::parse(std::string(keysBytes.begin(), keysBytes.end()));
const auto aesKey = util::base64Decode(keysJson[k::aesKey].get<std::string>());
const auto aesIv = util::base64Decode(keysJson[k::aesIv].get<std::string>());
std::string plain;
if (contains(req.url, "proxy.good.test")) {
++proxyPosts;
plain = R"({"ok":true,"via":"proxy"})";
} else {
++directPosts;
plain = R"({"http_status":404,"message":"blocked"})";
}
const std::vector<std::uint8_t> pv(plain.begin(), plain.end());
const auto cipher = crypto::aesEncryptCbc(pv, aesKey, aesIv);
resp.body.assign(cipher.begin(), cipher.end());
return resp;
}
private:
std::string m_priv;
};
}
int main()
{
const std::string pub = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_pub.pem");
const std::string priv = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_priv.pem");
auto mock = std::make_shared<FailoverMock>(priv);
Config cfg;
cfg.gatewayEndpoint = "https://gw.example.test/";
cfg.agwPublicKeyPem = pub;
cfg.isDevEnvironment = true;
cfg.s3PrimaryEndpoints = {"https://s3.example.test/"};
cfg.requestTimeoutMsecs = 5000;
cfg.httpClient = mock;
GatewayController client(std::move(cfg));
const std::string endpoint = "%1api/v1/test";
const FailoverContext ctx{"prem", "US"};
const std::string payload = R"({"hello":"world"})";
{
Response r = client.post(endpoint, payload, ctx);
CHECK(r.error == ErrorCode::NoError);
CHECK_EQ(r.body, std::string(R"({"ok":true,"via":"proxy"})"));
CHECK(mock->directPosts == 1);
CHECK(mock->storageGets >= 1);
CHECK(mock->healthGets >= 1);
CHECK(mock->proxyPosts == 1);
}
{
const int storageBefore = mock->storageGets;
const int healthBefore = mock->healthGets;
Response r = client.post(endpoint, payload, ctx);
CHECK(r.error == ErrorCode::NoError);
CHECK_EQ(r.body, std::string(R"({"ok":true,"via":"proxy"})"));
CHECK(mock->storageGets == storageBefore);
CHECK(mock->healthGets == healthBefore);
CHECK(mock->directPosts == 1);
}
return AGW_TEST_MAIN_RETURN();
}

View File

@@ -0,0 +1,103 @@
#include "agw_test.h"
#include <fstream>
#include <memory>
#include <sstream>
#include <string>
#include "agw/gateway_controller.h"
#include "agw/config.h"
#include "mock_gateway/mock_gateway.h"
using namespace agw;
namespace {
std::string readFile(const std::string &path)
{
std::ifstream f(path, std::ios::binary);
std::ostringstream ss;
ss << f.rdbuf();
return ss.str();
}
Config baseConfig(std::shared_ptr<IHttpClient> http, const std::string &pubPem)
{
Config c;
c.gatewayEndpoint = "gw.example.test";
c.agwPublicKeyPem = pubPem;
c.requestTimeoutMsecs = 5000;
c.httpClient = std::move(http);
return c;
}
}
int main()
{
const std::string pub = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_pub.pem");
const std::string priv = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_priv.pem");
const std::string endpoint = "https://%1/api/v1/test";
const FailoverContext ctx{"premium", "US"};
const std::string payload = R"({"hello":"world","n":42})";
{
auto mock = std::make_shared<agw_test::MockGateway>(priv);
mock->responsePlain = R"({"ok":true,"data":"hi"})";
std::string seenHost;
Config cfg = baseConfig(mock, pub);
cfg.onBeforeRequest = [&](const std::string &h) { seenHost = h; };
GatewayController client(std::move(cfg));
Response r = client.post(endpoint, payload, ctx);
CHECK(r.error == ErrorCode::NoError);
CHECK_EQ(r.body, std::string(R"({"ok":true,"data":"hi"})"));
CHECK_EQ(mock->lastDecryptedPayload, payload);
CHECK_EQ(mock->lastUrl, std::string("https://gw.example.test/api/v1/test"));
CHECK_EQ(seenHost, std::string("gw.example.test"));
CHECK(mock->requestCount == 1);
CHECK(mock->lastRequestId.size() == 36);
CHECK(mock->lastRequestId[14] == '4');
}
{
auto mock = std::make_shared<agw_test::MockGateway>(priv);
mock->responsePlain = R"({"http_status":409,"message":"limit"})";
GatewayController client(baseConfig(mock, pub));
Response r = client.post(endpoint, payload, ctx);
CHECK(r.error == ErrorCode::ApiConfigLimitError);
CHECK_EQ(r.body, std::string(R"({"http_status":409,"message":"limit"})"));
}
{
auto mock = std::make_shared<agw_test::MockGateway>(priv);
mock->simulateSsl = true;
GatewayController client(baseConfig(mock, pub));
Response r = client.post(endpoint, payload, ctx);
CHECK(r.error == ErrorCode::ApiConfigSslError);
}
{
auto mock = std::make_shared<agw_test::MockGateway>(priv);
mock->simulateTransport = TransportError::Timeout;
GatewayController client(baseConfig(mock, pub));
Response r = client.post(endpoint, payload, ctx);
CHECK(r.error == ErrorCode::ApiConfigTimeoutError);
}
{
auto mock = std::make_shared<agw_test::MockGateway>(priv);
Config cfg = baseConfig(mock, "not a pem key");
GatewayController client(std::move(cfg));
Response r = client.post(endpoint, payload, ctx);
CHECK(r.error == ErrorCode::ApiMissingAgwPublicKey);
CHECK(mock->requestCount == 0);
}
return AGW_TEST_MAIN_RETURN();
}

View File

@@ -0,0 +1,77 @@
#ifndef AGW_TEST_MOCK_GATEWAY_H
#define AGW_TEST_MOCK_GATEWAY_H
#include <string>
#include <vector>
#include "agw/http.h"
#include "crypto/aes.h"
#include "crypto/rsa.h"
#include "protocol/keys.h"
#include "util/base64.h"
#include "util/json.h"
namespace agw_test {
class MockGateway : public agw::IHttpClient {
public:
explicit MockGateway(std::string privateKeyPem) : m_priv(std::move(privateKeyPem)) {}
std::string responsePlain = "{\"ok\":true}";
bool simulateSsl = false;
agw::TransportError simulateTransport = agw::TransportError::None;
int httpStatusCode = 200;
std::string lastUrl;
std::string lastRequestId;
std::string lastDecryptedPayload;
int requestCount = 0;
agw::HttpResponse send(const agw::HttpRequest &req) override
{
++requestCount;
lastUrl = req.url;
for (const auto &h : req.headers) {
if (h.first == "X-Client-Request-ID") {
lastRequestId = h.second;
}
}
agw::HttpResponse resp;
resp.httpStatusCode = httpStatusCode;
if (simulateSsl) {
resp.sslError = true;
resp.error = agw::TransportError::ConnectionError;
return resp;
}
if (simulateTransport != agw::TransportError::None) {
resp.error = simulateTransport;
return resp;
}
namespace k = agw::protocol::keys;
agw::util::Json body = agw::util::Json::parse(req.body);
const auto keyCipher = agw::util::base64Decode(body[k::keyPayload].get<std::string>());
const auto keysBytes = agw::crypto::rsaDecryptPrivatePkcs1(keyCipher, m_priv);
agw::util::Json keysJson = agw::util::Json::parse(std::string(keysBytes.begin(), keysBytes.end()));
const auto aesKey = agw::util::base64Decode(keysJson[k::aesKey].get<std::string>());
const auto aesIv = agw::util::base64Decode(keysJson[k::aesIv].get<std::string>());
const auto apiCipher = agw::util::base64Decode(body[k::apiPayload].get<std::string>());
const auto payloadBytes = agw::crypto::aesDecryptCbc(apiCipher, aesKey, aesIv);
lastDecryptedPayload.assign(payloadBytes.begin(), payloadBytes.end());
const std::vector<std::uint8_t> respPlain(responsePlain.begin(), responsePlain.end());
const auto respCipher = agw::crypto::aesEncryptCbc(respPlain, aesKey, aesIv);
resp.body.assign(respCipher.begin(), respCipher.end());
resp.error = agw::TransportError::None;
return resp;
}
private:
std::string m_priv;
};
}
#endif

24765
agw-sdk/tests/third_party/nlohmann/json.hpp vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,48 @@
#include "agw_test.h"
#include <string>
#include "failover/bypass_policy.h"
using namespace agw;
using agw::failover::shouldBypassProxy;
namespace {
bool bypassBody(const std::string &body)
{
return shouldBypassProxy(TransportError::None, body, true);
}
}
int main()
{
CHECK(shouldBypassProxy(TransportError::None, "garbage", false) == true);
CHECK(shouldBypassProxy(TransportError::Timeout, R"({"http_status":200})", true) == true);
CHECK(shouldBypassProxy(TransportError::Canceled, R"({"http_status":200})", true) == true);
CHECK(bypassBody("<html><body>blocked</body></html>") == true);
CHECK(bypassBody(R"({"http_status":408})") == false);
CHECK(bypassBody(R"({"http_status":409})") == false);
CHECK(bypassBody(R"({"http_status":402})") == false);
CHECK(bypassBody(R"({"http_status":404,"message":"whatever"})") == true);
CHECK(bypassBody(R"({"http_status":404,"message":"No active configuration found for x"})") == false);
CHECK(bypassBody(R"({"http_status":404,"detail":"Account not found."})") == false);
CHECK(bypassBody(R"({"http_status":404,"message":"Session not found"})") == false);
CHECK(bypassBody(R"({"http_status":501})") == true);
CHECK(bypassBody(R"({"http_status":501,"message":"client version update is required"})") == false);
CHECK(bypassBody(R"({"http_status":422,"message":"Failed to retrieve subscription information. Is it activated?"})") == false);
CHECK(bypassBody(R"({"http_status":422,"message":"other"})") == true);
CHECK(bypassBody(R"({"http_status":200})") == false);
CHECK(bypassBody("plain ok") == false);
CHECK(shouldBypassProxy(TransportError::ConnectionError, R"({"http_status":200})", true) == true);
return AGW_TEST_MAIN_RETURN();
}

View File

@@ -0,0 +1,123 @@
#include "agw_test.h"
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include "crypto/aes.h"
#include "crypto/hash.h"
#include "crypto/rng.h"
#include "crypto/rsa.h"
#include "util/base64.h"
#include "util/uuid.h"
using namespace agw;
namespace {
std::vector<std::uint8_t> bytesOf(const std::string &s)
{
return std::vector<std::uint8_t>(s.begin(), s.end());
}
std::string readFile(const std::string &path)
{
std::ifstream f(path, std::ios::binary);
std::ostringstream ss;
ss << f.rdbuf();
return ss.str();
}
class FixedRng : public crypto::IRng {
public:
explicit FixedRng(std::vector<std::uint8_t> data) : m_data(std::move(data)) {}
std::vector<std::uint8_t> bytes(std::size_t n) override
{
std::vector<std::uint8_t> out(n);
for (std::size_t i = 0; i < n; ++i) {
out[i] = m_data[(m_pos + i) % m_data.size()];
}
m_pos += n;
return out;
}
private:
std::vector<std::uint8_t> m_data;
std::size_t m_pos = 0;
};
}
int main()
{
CHECK_EQ(util::base64Encode(std::string("")), std::string(""));
CHECK_EQ(util::base64Encode(std::string("f")), std::string("Zg=="));
CHECK_EQ(util::base64Encode(std::string("fo")), std::string("Zm8="));
CHECK_EQ(util::base64Encode(std::string("foo")), std::string("Zm9v"));
CHECK_EQ(util::base64Encode(std::string("foob")), std::string("Zm9vYg=="));
CHECK_EQ(util::base64Encode(std::string("fooba")), std::string("Zm9vYmE="));
CHECK_EQ(util::base64Encode(std::string("foobar")), std::string("Zm9vYmFy"));
{
std::vector<std::uint8_t> v{0xfb, 0xff, 0xbf};
CHECK_EQ(util::base64UrlEncodeNoPad(v), std::string("-_-_"));
CHECK_EQ(util::base64Encode(v), std::string("+/+/"));
}
{
auto v = bytesOf("any carnal pleasure.");
CHECK(util::base64Decode(util::base64Encode(v)) == v);
CHECK(util::base64Decode(util::base64UrlEncodeNoPad(v)) == v);
}
CHECK_EQ(crypto::toHex(crypto::sha512(bytesOf("abc"))),
std::string("ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a"
"2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f"));
{
std::vector<std::uint8_t> v{0x00, 0x01, 0xab, 0xff};
CHECK_EQ(crypto::toHex(v), std::string("0001abff"));
CHECK(crypto::fromHex("0001abff") == v);
}
{
auto key = crypto::fromHex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
auto iv = crypto::fromHex("101112131415161718191a1b1c1d1e1f");
auto pt = bytesOf("{\"hello\":\"world\"}");
auto ct = crypto::aesEncryptCbc(pt, key, iv);
CHECK_EQ(util::base64Encode(ct), std::string("2WHnAcP2N+l+jz7fbKyO46jjYUq7h98lxlTIT6K0Xg8="));
CHECK(crypto::aesDecryptCbc(ct, key, iv) == pt);
}
{
auto key = crypto::fromHex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
auto iv16 = crypto::fromHex("101112131415161718191a1b1c1d1e1f");
auto iv32 = crypto::fromHex("101112131415161718191a1b1c1d1e1fdeadbeefdeadbeefdeadbeefdeadbeef");
auto pt = bytesOf("{\"hello\":\"world\"}");
CHECK(crypto::aesEncryptCbc(pt, key, iv16) == crypto::aesEncryptCbc(pt, key, iv32));
}
{
std::string pub = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_pub.pem");
std::string priv = readFile(std::string(AGW_FIXTURES_DIR) + "/test_rsa_priv.pem");
CHECK(!pub.empty());
CHECK(!priv.empty());
auto msg = bytesOf("{\"aes_key\":\"...\",\"aes_iv\":\"...\",\"aes_salt\":\"...\"}");
auto ct = crypto::rsaEncryptPublicPkcs1(msg, pub);
auto rt = crypto::rsaDecryptPrivatePkcs1(ct, priv);
CHECK(rt == msg);
auto ct2 = crypto::rsaEncryptPublicPkcs1(msg, pub);
CHECK(ct != ct2);
}
{
FixedRng rng(std::vector<std::uint8_t>(16, 0xFF));
std::string u = util::makeUuidV4(rng);
CHECK_EQ(u, std::string("ffffffff-ffff-4fff-bfff-ffffffffffff"));
CHECK(u.size() == 36);
CHECK(u[14] == '4');
CHECK(u[19] == '8' || u[19] == '9' || u[19] == 'a' || u[19] == 'b');
}
return AGW_TEST_MAIN_RETURN();
}

View File

@@ -0,0 +1,54 @@
#include "agw_test.h"
#include <string>
#include "protocol/error_mapping.h"
using namespace agw;
using agw::protocol::mapResponseError;
namespace {
int code(ErrorCode e) { return static_cast<int>(e); }
ErrorCode mapBody(const std::string &body)
{
return mapResponseError(false, TransportError::None, body);
}
}
int main()
{
CHECK(mapResponseError(true, TransportError::None, "") == ErrorCode::ApiConfigSslError);
CHECK(mapResponseError(false, TransportError::Timeout, "") == ErrorCode::ApiConfigTimeoutError);
CHECK(mapResponseError(false, TransportError::Canceled, "") == ErrorCode::ApiConfigTimeoutError);
CHECK(mapResponseError(false, TransportError::OperationNotImplemented, "") == ErrorCode::ApiUpdateRequestError);
CHECK(mapResponseError(false, TransportError::ConnectionError, "") == ErrorCode::ApiConfigDownloadError);
CHECK(mapResponseError(false, TransportError::None, "") == ErrorCode::NoError);
CHECK(mapBody("not a json") == ErrorCode::NoError);
CHECK(mapBody(R"({"http_status":429})") == ErrorCode::ApiRateLimitError);
CHECK(mapBody(R"({"http_status":409})") == ErrorCode::ApiConfigLimitError);
CHECK(mapBody(R"({"http_status":409,"message":"Trial Subscription Already Used"})") == ErrorCode::ApiTrialAlreadyUsedError);
CHECK(mapBody(R"({"http_status":404})") == ErrorCode::ApiNotFoundError);
CHECK(mapBody(R"({"http_status":408})") == ErrorCode::ApiConfigTimeoutError);
CHECK(mapBody(R"({"http_status":501})") == ErrorCode::ApiUpdateRequestError);
CHECK(mapBody(R"({"http_status":422,"message":"Failed to retrieve subscription information. Is it activated?"})")
== ErrorCode::ApiSubscriptionExpiredError);
CHECK(mapBody(R"({"http_status":422,"message":"something else"})") == ErrorCode::ApiConfigDownloadError);
CHECK(mapBody(R"({"http_status":402,"message":"refresh_captcha"})") == ErrorCode::ApiCaptchaRefreshError);
CHECK(mapBody(R"({"http_status":402,"message":"invalid_captcha"})") == ErrorCode::ApiCaptchaInvalidError);
CHECK(mapBody(R"({"http_status":402,"captcha_id":"x"})") == ErrorCode::ApiCaptchaRequiredError);
CHECK(mapBody(R"({"http_status":402,"captcha_image":"x"})") == ErrorCode::ApiCaptchaRequiredError);
CHECK(mapBody(R"({"http_status":402,"message":"rate_limit_exceeded"})") == ErrorCode::ApiCaptchaRequiredError);
CHECK(mapBody(R"({"http_status":402,"message":"nope"})") == ErrorCode::ApiSubscriptionNotActiveError);
CHECK(mapBody(R"({"http_status":500})") == ErrorCode::ApiConfigDownloadError);
CHECK(mapBody(R"({"http_status":200})") == ErrorCode::NoError);
(void)code;
return AGW_TEST_MAIN_RETURN();
}

View File

@@ -0,0 +1,61 @@
#include "agw_test.h"
#include <string>
#include "util/json.h"
using namespace agw;
int main()
{
{
util::Json j;
j["aes_key"] = "KEY";
j["aes_iv"] = "IV";
j["aes_salt"] = "SALT";
const std::string expected =
"{\n"
" \"aes_iv\": \"IV\",\n"
" \"aes_key\": \"KEY\",\n"
" \"aes_salt\": \"SALT\"\n"
"}\n";
CHECK_EQ(util::qtIndentedDump(j), expected);
}
{
util::Json j;
j["key_payload"] = "K";
j["api_payload"] = "A";
const std::string expected =
"{\n"
" \"api_payload\": \"A\",\n"
" \"key_payload\": \"K\"\n"
"}\n";
CHECK_EQ(util::qtIndentedDump(j), expected);
}
{
util::Json j;
j["s"] = std::string("a\"b\\c\nd\te\x01");
const std::string expected =
"{\n"
" \"s\": \"a\\\"b\\\\c\\nd\\te\\u0001\"\n"
"}\n";
CHECK_EQ(util::qtIndentedDump(j), expected);
}
{
util::Json j;
j["outer"]["inner"] = "v";
const std::string expected =
"{\n"
" \"outer\": {\n"
" \"inner\": \"v\"\n"
" }\n"
"}\n";
CHECK_EQ(util::qtIndentedDump(j), expected);
}
return AGW_TEST_MAIN_RETURN();
}

View File

@@ -0,0 +1,87 @@
#include "agw_test.h"
#include <string>
#include <vector>
#include "crypto/aes.h"
#include "crypto/hash.h"
#include "failover/proxy_list.h"
#include "util/base64.h"
using namespace agw;
namespace {
std::vector<std::uint8_t> bytesOf(const std::string &s)
{
return std::vector<std::uint8_t>(s.begin(), s.end());
}
}
int main()
{
{
const std::vector<std::string> primary{"https://a/", "https://b/"};
const std::vector<std::string> fallback{"https://f/"};
const FailoverContext ctx{"prem", "US"};
const std::string enc =
util::base64UrlEncodeNoPad(bytesOf("endpoints-prem-US"));
const auto urls = failover::buildStorageUrls(primary, fallback, ctx);
const std::vector<std::string> expected{
"https://a/" + enc + ".json",
"https://b/" + enc + ".json",
"https://a/endpoints.json",
"https://b/endpoints.json",
"https://f/" + enc + ".json",
"https://f/endpoints.json",
};
CHECK(urls == expected);
}
{
const std::vector<std::string> primary{"https://a/", "https://b/"};
const std::vector<std::string> fallback{"https://f/"};
const FailoverContext ctx{"", ""};
const auto urls = failover::buildStorageUrls(primary, fallback, ctx);
const std::vector<std::string> expected{
"https://a/endpoints.json",
"https://b/endpoints.json",
"https://f/endpoints.json",
};
CHECK(urls == expected);
}
{
const auto list = failover::decodeProxyList(R"(["https://p1/","https://p2/"])", true, "");
const std::vector<std::string> expected{"https://p1/", "https://p2/"};
CHECK(list == expected);
CHECK(failover::decodeProxyList(R"({"x":1})", true, "").empty());
}
{
const std::string pub = "PUBKEYDATA-pem-like";
const std::string h = crypto::toHex(crypto::sha512(bytesOf(pub)));
const auto key = crypto::fromHex(h.substr(0, 64));
const auto iv = crypto::fromHex(h.substr(64, 32));
const std::string arr = R"(["https://prod1/","https://prod2/"])";
const auto cipher = crypto::aesEncryptCbc(bytesOf(arr), key, iv);
const std::string b64 = util::base64Encode(cipher);
const auto list = failover::decodeProxyList(b64, false, pub);
const std::vector<std::string> expected{"https://prod1/", "https://prod2/"};
CHECK(list == expected);
bool threw = false;
try {
failover::decodeProxyList("###not base64 cipher###", false, pub);
} catch (...) {
threw = true;
}
CHECK(threw);
}
return AGW_TEST_MAIN_RETURN();
}

View File

@@ -0,0 +1,33 @@
#include "agw_test.h"
#include <atomic>
#include <memory>
#include "util/thread_pool.h"
using namespace agw;
int main()
{
{
std::atomic<int> counter{0};
{
util::ThreadPool pool(4);
for (int i = 0; i < 1000; ++i) {
pool.submit([&counter] { counter.fetch_add(1, std::memory_order_relaxed); });
}
}
CHECK(counter.load() == 1000);
}
{
std::atomic<bool> ran{false};
{
util::ThreadPool pool(0);
pool.submit([&ran] { ran.store(true); });
}
CHECK(ran.load());
}
return AGW_TEST_MAIN_RETURN();
}

1
client/3rd/qtgamepad vendored Submodule

Submodule client/3rd/qtgamepad added at f72b3e0c62

View File

@@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
project(${PROJECT})
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER "Autogen")
set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen")
@@ -26,14 +25,14 @@ add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
add_definitions(-DPROD_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}")
add_definitions(-DPROD_S3_ENDPOINT="$ENV{PROD_S3_ENDPOINT}")
add_definitions(-DFALLBACK_S3_ENDPOINT="$ENV{FALLBACK_S3_ENDPOINT}")
add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
if(IOS)
set(PACKAGES ${PACKAGES} Multimedia)
endif()
add_definitions(-DFREE_V2_ENDPOINT="$ENV{FREE_V2_ENDPOINT}")
add_definitions(-DPREM_V1_ENDPOINT="$ENV{PREM_V1_ENDPOINT}")
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
set(PACKAGES ${PACKAGES} Widgets)
@@ -48,28 +47,30 @@ set(LIBS ${LIBS}
Qt6::Core5Compat Qt6::Concurrent
)
if(IOS)
set(LIBS ${LIBS} Qt6::Multimedia)
endif()
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
set(LIBS ${LIBS} Qt6::Widgets)
endif()
qt_standard_project_setup()
qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
target_include_directories(${PROJECT} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
)
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep)
endif()
qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
qt6_add_resources(QRC ${QRC}
${CMAKE_CURRENT_LIST_DIR}/images/images.qrc
${CMAKE_CURRENT_LIST_DIR}/images/flagKit.qrc
${CMAKE_CURRENT_LIST_DIR}/client_scripts/clientScripts.qrc
${CMAKE_CURRENT_LIST_DIR}/ui/qml/qml.qrc
${CMAKE_CURRENT_LIST_DIR}/server_scripts/serverScripts.qrc
)
# -- i18n begin
set(CMAKE_AUTORCC ON)
set(AMNEZIAVPN_TS_FILES
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru_RU.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts
@@ -81,26 +82,12 @@ set(AMNEZIAVPN_TS_FILES
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_hi_IN.ts
)
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
qt_create_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TS_SOURCES} ${AMNEZIAVPN_TS_FILES})
set(QM_FILE_LIST "")
foreach(FILE ${AMNEZIAVPN_QM_FILES})
get_filename_component(QM_FILE_NAME ${FILE} NAME)
list(APPEND QM_FILE_LIST "<file>${QM_FILE_NAME}</file>")
endforeach()
string(REPLACE ";" "" QM_FILE_LIST ${QM_FILE_LIST})
configure_file(${CMAKE_CURRENT_LIST_DIR}/translations/translations.qrc.in ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
qt6_add_translations(${PROJECT}
TS_FILES ${AMNEZIAVPN_TS_FILES}
RESOURCE_PREFIX "/translations"
)
# -- i18n end
if(IOS)
execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/ios/scripts/openvpn.sh args
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
endif()
set(IS_CI ${CI})
if(IS_CI)
message("Detected CI env")
@@ -110,8 +97,8 @@ if(IS_CI)
endif()
endif()
include(${CMAKE_CURRENT_LIST_DIR}/cmake/3rdparty.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/cmake/sources.cmake)
include_directories(
${CMAKE_CURRENT_LIST_DIR}/../ipc
@@ -120,165 +107,31 @@ include_directories(
${CMAKE_CURRENT_BINARY_DIR}
)
configure_file(${CMAKE_CURRENT_LIST_DIR}/../version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/migrations.h
${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc.h
${CMAKE_CURRENT_LIST_DIR}/amnezia_application.h
${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.h
${CMAKE_CURRENT_LIST_DIR}/core/defs.h
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.h
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.h
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.h
${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.h
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.h
${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.h
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h
${CMAKE_CURRENT_LIST_DIR}/protocols/qml_register_protocols.h
${CMAKE_CURRENT_LIST_DIR}/ui/pages.h
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h
${CMAKE_CURRENT_BINARY_DIR}/version.h
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.h
${CMAKE_CURRENT_LIST_DIR}/core/serialization/serialization.h
${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h
${CMAKE_CURRENT_LIST_DIR}/core/enums/apiEnums.h
${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.h
)
# Mozilla headres
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/mozilla/models/server.h
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/ipaddress.h
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/leakdetector.h
${CMAKE_CURRENT_LIST_DIR}/mozilla/controllerimpl.h
${CMAKE_CURRENT_LIST_DIR}/mozilla/localsocketcontroller.h
)
if(MACOS_NE)
message("MACOS_NE is ON")
add_definitions(-DQ_OS_MAC)
add_definitions(-DMACOS_NE)
message("Add macros for MacOS Network Extension")
else()
message("MACOS_NE is OFF")
endif()
include_directories(mozilla)
include_directories(mozilla/shared)
include_directories(mozilla/models)
if(NOT IOS)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.h
)
endif()
if(NOT ANDROID)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h
)
endif()
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/migrations.cpp
${CMAKE_CURRENT_LIST_DIR}/amnezia_application.cpp
${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.cpp
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.cpp
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.cpp
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.cpp
${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.cpp
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.cpp
${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.cpp
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.cpp
${CMAKE_CURRENT_LIST_DIR}/core/serialization/outbound.cpp
${CMAKE_CURRENT_LIST_DIR}/core/serialization/inbound.cpp
${CMAKE_CURRENT_LIST_DIR}/core/serialization/ss.cpp
${CMAKE_CURRENT_LIST_DIR}/core/serialization/ssd.cpp
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vless.cpp
${CMAKE_CURRENT_LIST_DIR}/core/serialization/trojan.cpp
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess.cpp
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess_new.cpp
${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.cpp
)
# Mozilla sources
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/mozilla/models/server.cpp
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/ipaddress.cpp
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/leakdetector.cpp
${CMAKE_CURRENT_LIST_DIR}/mozilla/localsocketcontroller.cpp
)
configure_file(${CMAKE_CURRENT_LIST_DIR}/../version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions(${PROJECT} PRIVATE "MZ_DEBUG")
endif()
if(NOT IOS)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.cpp
)
endif()
if(NOT ANDROID)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp
)
endif()
file(GLOB COMMON_FILES_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.h)
file(GLOB COMMON_FILES_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.cpp)
file(GLOB_RECURSE PAGE_LOGIC_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/pages_logic/*.h)
file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/pages_logic/*.cpp)
file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.h)
file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.cpp)
file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h
${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.h
${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.h
)
file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS
${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp
${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.cpp
${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.cpp
)
file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.h)
file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.cpp)
set(HEADERS ${HEADERS}
${COMMON_FILES_H}
${PAGE_LOGIC_H}
${CONFIGURATORS_H}
${UI_MODELS_H}
${UI_CONTROLLERS_H}
)
set(SOURCES ${SOURCES}
${COMMON_FILES_CPP}
${PAGE_LOGIC_CPP}
${CONFIGURATORS_CPP}
${UI_MODELS_CPP}
${UI_CONTROLLERS_CPP}
)
if(WIN32)
configure_file(
${CMAKE_CURRENT_LIST_DIR}/platforms/windows/amneziavpn.rc.in
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_windows.h
)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_windows.cpp
)
set(RESOURCES ${RESOURCES}
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
)
set(LIBS ${LIBS}
user32
rasapi32
@@ -292,7 +145,7 @@ if(WIN32)
endif()
if(APPLE)
cmake_policy(SET CMP0099 OLD)
cmake_policy(SET CMP0099 NEW)
cmake_policy(SET CMP0114 NEW)
if(NOT BUILD_OSX_APP_IDENTIFIER)
@@ -311,7 +164,10 @@ if(APPLE)
set(CMAKE_XCODE_GENERATE_SCHEME FALSE)
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${BUILD_VPN_DEVELOPMENT_TEAM})
set(CMAKE_XCODE_ATTRIBUTE_GROUP_ID_IOS ${BUILD_IOS_GROUP_IDENTIFIER})
if (BUILD_VPN_KEYCHAIN)
set(CMAKE_XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS "--keychain ${BUILD_VPN_KEYCHAIN}")
endif()
endif()
if(LINUX AND NOT ANDROID)
@@ -319,33 +175,8 @@ if(LINUX AND NOT ANDROID)
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux)
endif()
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
message("Client desktop build")
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
add_compile_definitions(AMNEZIA_DESKTOP)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/core/ipcclient.h
${CMAKE_CURRENT_LIST_DIR}/core/privileged_process.h
${CMAKE_CURRENT_LIST_DIR}/ui/systemtray_notificationhandler.h
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.h
)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/core/ipcclient.cpp
${CMAKE_CURRENT_LIST_DIR}/core/privileged_process.cpp
${CMAKE_CURRENT_LIST_DIR}/ui/systemtray_notificationhandler.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.cpp
)
endif()
if(ANDROID)
@@ -355,44 +186,95 @@ endif()
if(IOS)
include(cmake/ios.cmake)
include(cmake/ios-arch-fixup.cmake)
elseif(APPLE AND NOT IOS)
elseif(APPLE AND MACOS_NE)
include(cmake/macos_ne.cmake)
elseif(APPLE)
include(cmake/osxtools.cmake)
include(cmake/macos.cmake)
endif()
list(APPEND SOURCES ${CMAKE_CURRENT_LIST_DIR}/main.cpp)
target_link_libraries(${PROJECT} PRIVATE ${LIBS})
target_compile_definitions(${PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAME}>")
# deploy artifacts required to run the application to the debug build folder
if(WIN32)
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
set(DEPLOY_PLATFORM_PATH "windows/x64")
else()
set(DEPLOY_PLATFORM_PATH "windows/x32")
endif()
elseif(LINUX)
set(DEPLOY_PLATFORM_PATH "linux/client")
elseif(APPLE AND NOT IOS)
set(DEPLOY_PLATFORM_PATH "macos")
endif()
if(NOT IOS AND NOT ANDROID)
add_custom_command(
TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
${CMAKE_SOURCE_DIR}/deploy/data/${DEPLOY_PLATFORM_PATH}
$<TARGET_FILE_DIR:${PROJECT}>
COMMAND_EXPAND_LISTS
)
add_custom_command(
TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
${CMAKE_SOURCE_DIR}/client/3rd-prebuilt/deploy-prebuilt/${DEPLOY_PLATFORM_PATH}
$<TARGET_FILE_DIR:${PROJECT}>
COMMAND_EXPAND_LISTS
)
endif()
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
qt_finalize_target(${PROJECT})
# Finalize the executable so Qt can gather/deploy QML modules and plugins correctly (Android needs this).
if(COMMAND qt_import_qml_plugins)
qt_import_qml_plugins(${PROJECT})
endif()
if(COMMAND qt_finalize_executable)
qt_finalize_executable(${PROJECT})
else()
qt_finalize_target(${PROJECT})
endif()
install(TARGETS ${PROJECT}
DESTINATION ${CMAKE_INSTALL_BINDIR}
RUNTIME_DEPENDENCY_SET client_deps
COMPONENT AmneziaVPN
)
if(APPLE)
set(RUNTIME_DEPS_DIR ${CMAKE_INSTALL_BINDIR}/AmneziaVPN.app/Contents/Frameworks)
else()
set(RUNTIME_DEPS_DIR ${CMAKE_INSTALL_BINDIR})
endif()
install(RUNTIME_DEPENDENCY_SET client_deps
PRE_EXCLUDE_REGEXES
[[api-ms-win-.*]]
[[ext-ms-.*]]
[[kernel32\.dll]]
[[hvsifiletrust\.dll]]
[[libc\.so\..*]] [[libgcc_s\.so\..*]] [[libm\.so\..*]] [[libstdc\+\+\.so\..*]]
[[.*\.framework]]
[[^[Qq]t.*]]
POST_EXCLUDE_REGEXES
[[^.*[\\/]system32[\\/].*\.dll$]]
[[^/lib.*]]
[[^/usr/lib.*]]
DIRECTORIES ${CONAN_RUNTIME_LIB_DIRS}
COMPONENT AmneziaVPN
DESTINATION "${RUNTIME_DEPS_DIR}"
)
set(deploy_tool_options "")
if(WIN32)
set(deploy_tool_options "--force-openssl --force")
endif()
qt_generate_deploy_qml_app_script(
TARGET ${PROJECT}
OUTPUT_SCRIPT QT_DEPLOY_SCRIPT
NO_UNSUPPORTED_PLATFORM_ERROR
DEPLOY_TOOL_OPTIONS ${deploy_tool_options}
)
install(SCRIPT ${QT_DEPLOY_SCRIPT}
COMPONENT AmneziaVPN
)
if (APPLE AND NOT IOS AND NOT MACOS_NE)
list(APPEND OVPN_SCRIPTS "${CMAKE_SOURCE_DIR}/deploy/data/macos/update-resolv-conf.sh")
endif()
if (LINUX AND NOT ANDROID)
list(APPEND OVPN_SCRIPTS "${CMAKE_SOURCE_DIR}/deploy/data/linux/update-resolv-conf.sh")
endif()
if(OVPN_SCRIPTS)
add_custom_command(TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${OVPN_SCRIPTS}
"$<TARGET_FILE_DIR:${PROJECT}>"
)
install(FILES ${OVPN_SCRIPTS}
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT AmneziaVPN
PERMISSIONS
OWNER_READ OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE
)
endif()

View File

@@ -0,0 +1,315 @@
#include "amneziaApplication.h"
#include <QClipboard>
#include <QFontDatabase>
#include <QLocalServer>
#include <QLocalSocket>
#include <QMimeData>
#include <QQuickItem>
#include <QQuickStyle>
#include <QResource>
#include <QStandardPaths>
#include <QTextDocument>
#include <QTimer>
#include <QTranslator>
#include <QEvent>
#include <QDir>
#include <QSettings>
#include <QtQuick/QQuickWindow>
#include <QWindow>
#include "core/protocols/qmlRegisterProtocols.h"
#include "logger.h"
#include "ui/controllers/qml/pageController.h"
#include "ui/models/installedAppsModel.h"
#include "version.h"
#include "platforms/ios/QRCodeReaderBase.h"
bool AmneziaApplication::m_forceQuit = false;
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv),
m_optAutostart({QStringLiteral("a"), QStringLiteral("autostart")}, QStringLiteral("System autostart")),
m_optCleanup ({QStringLiteral("c"), QStringLiteral("cleanup")}, QStringLiteral("Cleanup logs")),
m_optConnect ({QStringLiteral("connect")}, QStringLiteral("Connect to server by index on startup"), QStringLiteral("index")),
m_optImport ({QStringLiteral("import")}, QStringLiteral("Import configuration from data string"), QStringLiteral("data"))
{
setDesktopFileName(QStringLiteral(APPLICATION_NAME));
setQuitOnLastWindowClosed(false);
// Fix config file permissions
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
{
QSettings s(ORGANIZATION_NAME, APPLICATION_NAME);
s.setValue("permFixed", true);
}
QString configLoc1 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/"
+ APPLICATION_NAME + ".conf";
QFile::setPermissions(configLoc1, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
QString configLoc2 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/"
+ APPLICATION_NAME + "/" + APPLICATION_NAME + ".conf";
QFile::setPermissions(configLoc2, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
#endif
m_settings = new SecureQSettings(ORGANIZATION_NAME, APPLICATION_NAME, this);
m_nam = new QNetworkAccessManager(this);
}
AmneziaApplication::~AmneziaApplication()
{
#ifdef AMNEZIA_DESKTOP
if (m_vpnConnection && m_vpnConnectionThread.isRunning()) {
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectSlots", Qt::BlockingQueuedConnection);
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::BlockingQueuedConnection);
}
#endif
m_vpnConnectionThread.requestInterruption();
m_vpnConnectionThread.quit();
if (!m_vpnConnectionThread.wait(3000)) {
m_vpnConnectionThread.terminate();
m_vpnConnectionThread.wait(500);
}
if (m_engine) {
delete m_engine;
}
}
#ifdef Q_OS_ANDROID
namespace {
static void clearQtCaches()
{
const QString cacheRoot = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
if (!cacheRoot.isEmpty()) {
QDir(cacheRoot + "/QtShaderCache").removeRecursively();
QDir(cacheRoot + "/qmlcache").removeRecursively();
}
}
}
#endif
void AmneziaApplication::init()
{
m_engine = new QQmlApplicationEngine;
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
QObject::connect(
m_engine, &QQmlApplicationEngine::objectCreated, this,
[this, url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl) {
QCoreApplication::exit(-1);
return;
}
// install filter on main window
if (auto win = qobject_cast<QQuickWindow*>(obj)) {
win->installEventFilter(this);
#ifdef Q_OS_ANDROID
QObject::connect(win, &QQuickWindow::sceneGraphError,
[](QQuickWindow::SceneGraphError, const QString &msg) {
qWarning() << "Scene graph error (suppressed):" << msg;
});
// Keep graphics context alive across hide/show cycles to avoid
// eglSwapBuffers/makeCurrent being called on a context Android has reclaimed.
win->setPersistentSceneGraph(true);
win->setPersistentGraphics(true);
#endif
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
win->show();
#else
if (!m_coreController || !m_coreController->pageController()->shouldStartMinimized()) {
win->show();
}
#endif
}
},
Qt::QueuedConnection);
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
#ifdef MACOS_NE
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", true);
#else
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", false);
#endif
m_vpnConnection.reset(new VpnConnection(nullptr, nullptr));
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start();
m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
m_engine->addImportPath("qrc:/ui/qml/Modules/");
if (m_parser.isSet(m_optImport)) {
const QString data = m_parser.value(m_optImport);
if (!data.isEmpty()) {
if (m_coreController) {
m_coreController->importConfigFromData(data);
}
}
}
m_engine->load(url);
m_coreController->setQmlRoot();
#ifdef Q_OS_WIN //TODO
if (m_parser.isSet(m_optAutostart))
m_coreController->pageController()->showOnStartup();
else
emit m_coreController->pageController()->raiseMainWindow();
#else
m_coreController->pageController()->showOnStartup();
#endif
// Android TextArea clipboard workaround
// Text from TextArea always has "text/html" mime-type:
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1865
// Next, html is created for this mime-type:
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1885
// And this html goes to the Androids clipboard, i.e. text from TextArea is always copied as richText:
// /qt/6.6.1/Src/qtbase/src/plugins/platforms/android/androidjniclipboard.cpp:46
// So we catch all the copies to the clipboard and clear them from "text/html"
#ifdef Q_OS_ANDROID
connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, []() {
auto clipboard = QGuiApplication::clipboard();
if (clipboard->mimeData()->hasHtml()) {
clipboard->setText(clipboard->text());
}
});
#endif
if (m_parser.isSet(m_optConnect)) {
bool ok = false;
int idx = m_parser.value(m_optConnect).toInt(&ok);
if (ok) {
QTimer::singleShot(0, this, [this, idx]() {
if (m_coreController) {
m_coreController->openConnectionByIndex(idx);
}
});
}
}
}
void AmneziaApplication::registerTypes()
{
qRegisterMetaType<ServerCredentials>("ServerCredentials");
qRegisterMetaType<DockerContainer>("DockerContainer");
using namespace amnezia::ProtocolEnumNS;
qRegisterMetaType<TransportProto>("TransportProto");
qRegisterMetaType<Proto>("Proto");
qRegisterMetaType<ServiceType>("ServiceType");
qmlRegisterType<QRCodeReader>("QRCodeReader", 1, 0, "QRCodeReader");
m_containerProps.reset(new ContainerProps());
qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps.get());
m_protocolProps.reset(new ProtocolProps());
qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps.get());
qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0,
"ContainersModelFilters");
qmlRegisterType<InstalledAppsModel>("InstalledAppsModel", 1, 0, "InstalledAppsModel");
amnezia::declareQmlProtocolEnum();
Vpn::declareQmlVpnConnectionStateEnum();
PageLoader::declareQmlPageEnum();
}
void AmneziaApplication::loadFonts()
{
QQuickStyle::setStyle("Basic");
QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf");
}
bool AmneziaApplication::parseCommands()
{
m_parser.setApplicationDescription(APPLICATION_NAME);
m_parser.addHelpOption();
m_parser.addVersionOption();
m_parser.addOption(m_optAutostart);
m_parser.addOption(m_optCleanup);
m_parser.addOption(m_optConnect);
m_parser.addOption(m_optImport);
m_parser.process(*this);
if (m_parser.isSet(m_optCleanup)) {
Logger::cleanUp();
QTimer::singleShot(100, this, [this] { quit(); });
exec();
return false;
}
return true;
}
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
void AmneziaApplication::startLocalServer() {
const QString serverName("AmneziaVPNInstance");
QLocalServer::removeServer(serverName);
QLocalServer *server = new QLocalServer(this);
server->listen(serverName);
QObject::connect(server, &QLocalServer::newConnection, this, [server, this]() {
if (server) {
QLocalSocket *clientConnection = server->nextPendingConnection();
clientConnection->deleteLater();
}
emit m_coreController->pageController()->raiseMainWindow(); //TODO
});
}
#endif
bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::Close) {
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
quit();
#else
if (m_forceQuit) {
quit();
} else {
if (m_coreController && m_coreController->pageController()) {
m_coreController->pageController()->hideMainWindow();
}
}
#endif
return true; // eat the close
}
// call base QObject::eventFilter
return QObject::eventFilter(watched, event);
}
void AmneziaApplication::forceQuit()
{
m_forceQuit = true;
quit();
}
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
{
return m_engine;
}
QNetworkAccessManager *AmneziaApplication::networkManager()
{
return m_nam;
}
QClipboard *AmneziaApplication::getClipboard()
{
return this->clipboard();
}

View File

@@ -0,0 +1,78 @@
#ifndef AMNEZIA_APPLICATION_H
#define AMNEZIA_APPLICATION_H
#include <QCommandLineParser>
#include <QNetworkAccessManager>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QThread>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QGuiApplication>
#else
#include <QApplication>
#endif
#include <QClipboard>
#include "core/controllers/coreController.h"
#include "secureQSettings.h"
#include "vpnConnection.h"
#include "ui/models/containerProps.h"
#include "ui/models/protocolProps.h"
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#define AMNEZIA_BASE_CLASS QGuiApplication
#else
#define AMNEZIA_BASE_CLASS QApplication
#endif
class AmneziaApplication : public AMNEZIA_BASE_CLASS
{
Q_OBJECT
public:
AmneziaApplication(int &argc, char *argv[]);
virtual ~AmneziaApplication();
void init();
void registerTypes();
void loadFonts();
bool parseCommands();
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
void startLocalServer();
#endif
QQmlApplicationEngine *qmlEngine() const;
QNetworkAccessManager *networkManager();
QClipboard *getClipboard();
public slots:
void forceQuit();
private:
static bool m_forceQuit;
QQmlApplicationEngine *m_engine {};
SecureQSettings* m_settings;
QScopedPointer<CoreController> m_coreController;
QSharedPointer<ContainerProps> m_containerProps;
QSharedPointer<ProtocolProps> m_protocolProps;
QCommandLineParser m_parser;
QCommandLineOption m_optAutostart;
QCommandLineOption m_optCleanup;
QCommandLineOption m_optConnect;
QCommandLineOption m_optImport;
QSharedPointer<VpnConnection> m_vpnConnection;
QThread m_vpnConnectionThread;
QNetworkAccessManager *m_nam;
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
};
#endif // AMNEZIA_APPLICATION_H

View File

@@ -1,463 +0,0 @@
#include "amnezia_application.h"
#include <QClipboard>
#include <QFontDatabase>
#include <QMimeData>
#include <QQuickItem>
#include <QQuickStyle>
#include <QResource>
#include <QStandardPaths>
#include <QTextDocument>
#include <QTimer>
#include <QTranslator>
#include <QLocalSocket>
#include <QLocalServer>
#include "logger.h"
#include "ui/models/installedAppsModel.h"
#include "version.h"
#include "platforms/ios/QRCodeReaderBase.h"
#if defined(Q_OS_ANDROID)
#include "core/installedAppsImageProvider.h"
#include "platforms/android/android_controller.h"
#endif
#include "protocols/qml_register_protocols.h"
#if defined(Q_OS_IOS)
#include "platforms/ios/ios_controller.h"
#include <AmneziaVPN-Swift.h>
#endif
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
{
setQuitOnLastWindowClosed(false);
// Fix config file permissions
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
{
QSettings s(ORGANIZATION_NAME, APPLICATION_NAME);
s.setValue("permFixed", true);
}
QString configLoc1 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/"
+ APPLICATION_NAME + ".conf";
QFile::setPermissions(configLoc1, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
QString configLoc2 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/"
+ APPLICATION_NAME + "/" + APPLICATION_NAME + ".conf";
QFile::setPermissions(configLoc2, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
#endif
m_settings = std::shared_ptr<Settings>(new Settings);
m_nam = new QNetworkAccessManager(this);
}
AmneziaApplication::~AmneziaApplication()
{
m_vpnConnectionThread.quit();
m_vpnConnectionThread.wait(3000);
if (m_engine) {
QObject::disconnect(m_engine, 0, 0, 0);
delete m_engine;
}
}
void AmneziaApplication::init()
{
m_engine = new QQmlApplicationEngine;
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
QObject::connect(
m_engine, &QQmlApplicationEngine::objectCreated, this,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
},
Qt::QueuedConnection);
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
m_vpnConnection.reset(new VpnConnection(m_settings));
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start();
initModels();
loadTranslator();
initControllers();
#ifdef Q_OS_ANDROID
if (!AndroidController::initLogging()) {
qFatal("Android logging initialization failed");
}
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs);
AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled());
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, AndroidController::instance(), &AndroidController::setScreenshotsEnabled);
connect(m_settings.get(), &Settings::serverRemoved, AndroidController::instance(), &AndroidController::resetLastServer);
connect(m_settings.get(), &Settings::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); });
connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) {
m_connectionController->onConnectionStateChanged(state);
if (m_vpnConnection)
m_vpnConnection->restoreConnection();
});
if (!AndroidController::instance()->initialize()) {
qFatal("Android controller initialization failed");
}
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) {
emit m_pageController->goToPageHome();
m_importController->extractConfigFromData(data);
data.clear();
emit m_pageController->goToPageViewConfig();
});
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
#endif
#ifdef Q_OS_IOS
IosController::Instance()->initialize();
connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) {
emit m_pageController->goToPageHome();
m_importController->extractConfigFromData(data);
emit m_pageController->goToPageViewConfig();
});
connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) {
emit m_pageController->goToPageHome();
m_pageController->goToPageSettingsBackup();
emit m_settingsController->importBackupFromOutside(filePath);
});
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); });
#endif
#ifndef Q_OS_ANDROID
m_notificationHandler.reset(NotificationHandler::create(nullptr));
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
&NotificationHandler::setConnectionState);
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow);
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
static_cast<void (ConnectionController::*)()>(&ConnectionController::openConnection));
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
&ConnectionController::closeConnection);
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
#endif
m_engine->addImportPath("qrc:/ui/qml/Modules/");
m_engine->load(url);
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
bool enabled = m_settings->isSaveLogs();
#ifndef Q_OS_ANDROID
if (enabled) {
if (!Logger::init(false)) {
qWarning() << "Initialization of debug subsystem failed";
}
}
#endif
Logger::setServiceLogsEnabled(enabled);
#ifdef Q_OS_WIN
if (m_parser.isSet("a"))
m_pageController->showOnStartup();
else
emit m_pageController->raiseMainWindow();
#else
m_pageController->showOnStartup();
#endif
// Android TextArea clipboard workaround
// Text from TextArea always has "text/html" mime-type:
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1865
// Next, html is created for this mime-type:
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1885
// And this html goes to the Androids clipboard, i.e. text from TextArea is always copied as richText:
// /qt/6.6.1/Src/qtbase/src/plugins/platforms/android/androidjniclipboard.cpp:46
// So we catch all the copies to the clipboard and clear them from "text/html"
#ifdef Q_OS_ANDROID
connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, []() {
auto clipboard = QGuiApplication::clipboard();
if (clipboard->mimeData()->hasHtml()) {
clipboard->setText(clipboard->text());
}
});
#endif
}
void AmneziaApplication::registerTypes()
{
qRegisterMetaType<ServerCredentials>("ServerCredentials");
qRegisterMetaType<DockerContainer>("DockerContainer");
qRegisterMetaType<TransportProto>("TransportProto");
qRegisterMetaType<Proto>("Proto");
qRegisterMetaType<ServiceType>("ServiceType");
declareQmlProtocolEnum();
declareQmlContainerEnum();
qmlRegisterType<QRCodeReader>("QRCodeReader", 1, 0, "QRCodeReader");
m_containerProps.reset(new ContainerProps());
qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps.get());
m_protocolProps.reset(new ProtocolProps());
qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps.get());
qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0,
"ContainersModelFilters");
qmlRegisterType<InstalledAppsModel>("InstalledAppsModel", 1, 0, "InstalledAppsModel");
Vpn::declareQmlVpnConnectionStateEnum();
PageLoader::declareQmlPageEnum();
}
void AmneziaApplication::loadFonts()
{
QQuickStyle::setStyle("Basic");
QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf");
}
void AmneziaApplication::loadTranslator()
{
auto locale = m_settings->getAppLanguage();
m_translator.reset(new QTranslator());
updateTranslator(locale);
}
void AmneziaApplication::updateTranslator(const QLocale &locale)
{
if (!m_translator->isEmpty()) {
QCoreApplication::removeTranslator(m_translator.get());
}
QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm";
if (m_translator->load(strFileName)) {
if (QCoreApplication::installTranslator(m_translator.get())) {
m_settings->setAppLanguage(locale);
}
} else {
m_settings->setAppLanguage(QLocale::English);
}
m_engine->retranslate();
emit translationsUpdated();
}
bool AmneziaApplication::parseCommands()
{
m_parser.setApplicationDescription(APPLICATION_NAME);
m_parser.addHelpOption();
m_parser.addVersionOption();
QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" };
m_parser.addOption(c_autostart);
QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" };
m_parser.addOption(c_cleanup);
m_parser.process(*this);
if (m_parser.isSet(c_cleanup)) {
Logger::cleanUp();
QTimer::singleShot(100, this, [this] { quit(); });
exec();
return false;
}
return true;
}
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
void AmneziaApplication::startLocalServer() {
const QString serverName("AmneziaVPNInstance");
QLocalServer::removeServer(serverName);
QLocalServer* server = new QLocalServer(this);
server->listen(serverName);
QObject::connect(server, &QLocalServer::newConnection, this, [server, this]() {
if (server) {
QLocalSocket* clientConnection = server->nextPendingConnection();
clientConnection->deleteLater();
}
emit m_pageController->raiseMainWindow();
});
}
#endif
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
{
return m_engine;
}
void AmneziaApplication::initModels()
{
m_containersModel.reset(new ContainersModel(this));
m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get());
m_defaultServerContainersModel.reset(new ContainersModel(this));
m_engine->rootContext()->setContextProperty("DefaultServerContainersModel", m_defaultServerContainersModel.get());
m_serversModel.reset(new ServersModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
&ContainersModel::updateModel);
m_serversModel->resetModel();
m_languageModel.reset(new LanguageModel(m_settings, this));
m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator);
connect(this, &AmneziaApplication::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated);
m_sitesModel.reset(new SitesModel(m_settings, this));
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
m_openVpnConfigModel.reset(new OpenVpnConfigModel(this));
m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get());
m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this));
m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get());
m_cloakConfigModel.reset(new CloakConfigModel(this));
m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get());
m_wireGuardConfigModel.reset(new WireGuardConfigModel(this));
m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get());
m_awgConfigModel.reset(new AwgConfigModel(this));
m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
m_xrayConfigModel.reset(new XrayConfigModel(this));
m_engine->rootContext()->setContextProperty("XrayConfigModel", m_xrayConfigModel.get());
#ifdef Q_OS_WINDOWS
m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
#endif
m_sftpConfigModel.reset(new SftpConfigModel(this));
m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
m_socks5ConfigModel.reset(new Socks5ProxyConfigModel(this));
m_engine->rootContext()->setContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel.get());
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
&ServersModel::clearCachedProfile);
m_apiServicesModel.reset(new ApiServicesModel(this));
m_engine->rootContext()->setContextProperty("ApiServicesModel", m_apiServicesModel.get());
m_apiCountryModel.reset(new ApiCountryModel(this));
m_engine->rootContext()->setContextProperty("ApiCountryModel", m_apiCountryModel.get());
connect(m_serversModel.get(), &ServersModel::updateApiLanguageModel, this, [this]() {
m_apiCountryModel->updateModel(m_serversModel->getProcessedServerData("apiAvailableCountries").toJsonArray(),
m_serversModel->getProcessedServerData("apiServerCountryCode").toString());
});
connect(m_serversModel.get(), &ServersModel::updateApiServicesModel, this,
[this]() { m_apiServicesModel->updateModel(m_serversModel->getProcessedServerData("apiConfig").toJsonObject()); });
}
void AmneziaApplication::initControllers()
{
m_connectionController.reset(
new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
connect(m_connectionController.get(), qOverload<const QString &>(&ConnectionController::connectionErrorOccurred), this,
[this](const QString &errorMessage) {
emit m_pageController->showErrorMessage(errorMessage);
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
});
connect(m_connectionController.get(), qOverload<ErrorCode>(&ConnectionController::connectionErrorOccurred), this,
[this](ErrorCode errorCode) {
emit m_pageController->showErrorMessage(errorCode);
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
});
connect(m_connectionController.get(), &ConnectionController::connectButtonClicked, m_connectionController.get(),
&ConnectionController::toggleConnection, Qt::QueuedConnection);
m_pageController.reset(new PageController(m_serversModel, m_settings));
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel,
m_apiServicesModel, m_settings));
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
&PageController::showPassphraseRequestDrawer);
connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(),
&InstallController::setEncryptedPassphrase);
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
&ConnectionController::onCurrentContainerUpdated);
connect(m_installController.get(), &InstallController::updateServerFromApiFinished, this, [this]() {
disconnect(m_reloadConfigErrorOccurredConnection);
emit m_connectionController->configFromApiUpdated();
});
connect(m_connectionController.get(), &ConnectionController::updateApiConfigFromGateway, this, [this]() {
m_reloadConfigErrorOccurredConnection = connect(
m_installController.get(), qOverload<ErrorCode>(&InstallController::installationErrorOccurred), this,
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); },
static_cast<Qt::ConnectionType>(Qt::AutoConnection || Qt::SingleShotConnection));
m_installController->updateServiceFromApi(m_serversModel->getDefaultServerIndex(), "", "");
});
connect(m_connectionController.get(), &ConnectionController::updateApiConfigFromTelegram, this, [this]() {
m_reloadConfigErrorOccurredConnection = connect(
m_installController.get(), qOverload<ErrorCode>(&InstallController::installationErrorOccurred), this,
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); },
static_cast<Qt::ConnectionType>(Qt::AutoConnection || Qt::SingleShotConnection));
m_serversModel->removeApiConfig(m_serversModel->getDefaultServerIndex());
m_installController->updateServiceFromTelegram(m_serversModel->getDefaultServerIndex());
});
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated);
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings));
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
m_settingsController.reset(
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
}
connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(), &ServersModel::toggleAmneziaDns);
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
m_systemController.reset(new SystemController(m_settings));
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
}

View File

@@ -1,141 +0,0 @@
#ifndef AMNEZIA_APPLICATION_H
#define AMNEZIA_APPLICATION_H
#include <QCommandLineParser>
#include <QNetworkAccessManager>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QThread>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QGuiApplication>
#else
#include <QApplication>
#endif
#include "settings.h"
#include "vpnconnection.h"
#include "ui/controllers/connectionController.h"
#include "ui/controllers/exportController.h"
#include "ui/controllers/importController.h"
#include "ui/controllers/installController.h"
#include "ui/controllers/pageController.h"
#include "ui/controllers/settingsController.h"
#include "ui/controllers/sitesController.h"
#include "ui/controllers/systemController.h"
#include "ui/controllers/appSplitTunnelingController.h"
#include "ui/models/containers_model.h"
#include "ui/models/languageModel.h"
#include "ui/models/protocols/cloakConfigModel.h"
#ifndef Q_OS_ANDROID
#include "ui/notificationhandler.h"
#endif
#ifdef Q_OS_WINDOWS
#include "ui/models/protocols/ikev2ConfigModel.h"
#endif
#include "ui/models/protocols/awgConfigModel.h"
#include "ui/models/protocols/openvpnConfigModel.h"
#include "ui/models/protocols/shadowsocksConfigModel.h"
#include "ui/models/protocols/wireguardConfigModel.h"
#include "ui/models/protocols/xrayConfigModel.h"
#include "ui/models/protocols_model.h"
#include "ui/models/servers_model.h"
#include "ui/models/services/sftpConfigModel.h"
#include "ui/models/services/socks5ProxyConfigModel.h"
#include "ui/models/sites_model.h"
#include "ui/models/clientManagementModel.h"
#include "ui/models/appSplitTunnelingModel.h"
#include "ui/models/apiServicesModel.h"
#include "ui/models/apiCountryModel.h"
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#define AMNEZIA_BASE_CLASS QGuiApplication
#else
#define AMNEZIA_BASE_CLASS QApplication
#endif
class AmneziaApplication : public AMNEZIA_BASE_CLASS
{
Q_OBJECT
public:
AmneziaApplication(int &argc, char *argv[]);
virtual ~AmneziaApplication();
void init();
void registerTypes();
void loadFonts();
void loadTranslator();
void updateTranslator(const QLocale &locale);
bool parseCommands();
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
void startLocalServer();
#endif
QQmlApplicationEngine *qmlEngine() const;
QNetworkAccessManager *manager() { return m_nam; }
signals:
void translationsUpdated();
private:
void initModels();
void initControllers();
QQmlApplicationEngine *m_engine {};
std::shared_ptr<Settings> m_settings;
QSharedPointer<ContainerProps> m_containerProps;
QSharedPointer<ProtocolProps> m_protocolProps;
QSharedPointer<QTranslator> m_translator;
QCommandLineParser m_parser;
QSharedPointer<ContainersModel> m_containersModel;
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<LanguageModel> m_languageModel;
QSharedPointer<ProtocolsModel> m_protocolsModel;
QSharedPointer<SitesModel> m_sitesModel;
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
QSharedPointer<ClientManagementModel> m_clientManagementModel;
QSharedPointer<ApiServicesModel> m_apiServicesModel;
QSharedPointer<ApiCountryModel> m_apiCountryModel;
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
QScopedPointer<CloakConfigModel> m_cloakConfigModel;
QScopedPointer<XrayConfigModel> m_xrayConfigModel;
QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
QScopedPointer<AwgConfigModel> m_awgConfigModel;
#ifdef Q_OS_WINDOWS
QScopedPointer<Ikev2ConfigModel> m_ikev2ConfigModel;
#endif
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
QScopedPointer<Socks5ProxyConfigModel> m_socks5ConfigModel;
QSharedPointer<VpnConnection> m_vpnConnection;
QThread m_vpnConnectionThread;
#ifndef Q_OS_ANDROID
QScopedPointer<NotificationHandler> m_notificationHandler;
#endif
QScopedPointer<ConnectionController> m_connectionController;
QScopedPointer<PageController> m_pageController;
QScopedPointer<InstallController> m_installController;
QScopedPointer<ImportController> m_importController;
QScopedPointer<ExportController> m_exportController;
QScopedPointer<SettingsController> m_settingsController;
QScopedPointer<SitesController> m_sitesController;
QScopedPointer<SystemController> m_systemController;
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
QNetworkAccessManager *m_nam;
QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
};
#endif // AMNEZIA_APPLICATION_H

View File

@@ -45,7 +45,8 @@
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
android:launchMode="singleInstance"
android:windowSoftInputMode="stateUnchanged|adjustResize"
android:windowSoftInputMode="adjustResize|stateUnchanged"
android:enableOnBackInvokedCallback="false"
android:exported="true">
<intent-filter>
@@ -91,6 +92,13 @@
android:exported="false"
android:theme="@style/Translucent" />
<activity android:name=".TvFilePicker"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:taskAffinity=""
android:exported="false"
android:theme="@style/Translucent" />
<activity
android:name=".ImportConfigActivity"
android:excludeFromRecents="true"
@@ -207,4 +215,4 @@
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" />
</provider>
</application>
</manifest>
</manifest>

View File

@@ -39,6 +39,7 @@ android {
// keeps language resources for only the locales specified below
resourceConfigurations += listOf("en", "ru", "b+zh+Hans")
ndk.abiFilters += qtTargetAbiList.split(",")
}
sourceSets {
@@ -52,50 +53,12 @@ android {
}
}
signingConfigs {
register("release") {
storeFile = providers.environmentVariable("ANDROID_KEYSTORE_PATH").orNull?.let { file(it) }
storePassword = providers.environmentVariable("ANDROID_KEYSTORE_KEY_PASS").orNull
keyAlias = providers.environmentVariable("ANDROID_KEYSTORE_KEY_ALIAS").orNull
keyPassword = providers.environmentVariable("ANDROID_KEYSTORE_KEY_PASS").orNull
}
}
buildTypes {
release {
// exclude coroutine debug resource from release build
packaging {
resources.excludes += "DebugProbesKt.bin"
}
signingConfig = signingConfigs["release"]
}
create("fdroid") {
initWith(getByName("release"))
signingConfig = null
matchingFallbacks += "release"
}
}
splits {
abi {
isEnable = true
reset()
include(*qtTargetAbiList.split(',').toTypedArray())
isUniversalApk = false
}
}
// fix for Qt Creator to allow deploying the application to a device
// to enable this fix, add the line outputBaseName=android-build to local.properties
if (outputBaseName.isNotEmpty()) {
applicationVariants.all {
outputs.map { it as BaseVariantOutputImpl }
.forEach { output ->
if (output.outputFileName.endsWith(".apk")) {
output.outputFileName = "$outputBaseName-${buildType.name}.apk"
}
}
}
}
@@ -111,7 +74,6 @@ dependencies {
implementation(project(":wireguard"))
implementation(project(":awg"))
implementation(project(":openvpn"))
implementation(project(":cloak"))
implementation(project(":xray"))
implementation(libs.androidx.core)
implementation(libs.androidx.activity)

View File

@@ -1,18 +0,0 @@
plugins {
id(libs.plugins.android.library.get().pluginId)
id(libs.plugins.kotlin.android.get().pluginId)
}
kotlin {
jvmToolchain(17)
}
android {
namespace = "org.amnezia.vpn.protocol.cloak"
}
dependencies {
compileOnly(project(":utils"))
compileOnly(project(":protocolApi"))
implementation(project(":openvpn"))
}

View File

@@ -1,45 +0,0 @@
package org.amnezia.vpn.protocol.cloak
import android.util.Base64
import net.openvpn.ovpn3.ClientAPI_Config
import org.amnezia.vpn.protocol.openvpn.OpenVpn
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
import org.json.JSONObject
class Cloak : OpenVpn() {
override fun internalInit() {
super.internalInit()
if (!isInitialized) loadSharedLibrary(context, "ck-ovpn-plugin")
}
override fun parseConfig(config: JSONObject): ClientAPI_Config {
val openVpnConfig = ClientAPI_Config()
val openVpnConfigStr = config.getJSONObject("openvpn_config_data").getString("config")
val cloakConfigJson = checkCloakJson(config.getJSONObject("cloak_config_data"))
val cloakConfigStr = Base64.encodeToString(cloakConfigJson.toString().toByteArray(), Base64.DEFAULT)
val configStr = "$openVpnConfigStr\n<cloak>\n$cloakConfigStr\n</cloak>\n"
openVpnConfig.usePluggableTransports = true
openVpnConfig.content = configStr
return openVpnConfig
}
private fun checkCloakJson(cloakConfigJson: JSONObject): JSONObject {
cloakConfigJson.put("NumConn", 1)
cloakConfigJson.put("ProxyMethod", "openvpn")
if (cloakConfigJson.has("port")) {
val port = cloakConfigJson["port"]
cloakConfigJson.remove("port")
cloakConfigJson.put("RemotePort", port)
}
if (cloakConfigJson.has("remote")) {
val remote = cloakConfigJson["remote"]
cloakConfigJson.remove("remote")
cloakConfigJson.put("RemoteHost", remote)
}
return cloakConfigJson
}
}

View File

@@ -1,11 +1,11 @@
[versions]
agp = "8.5.2"
agp = "8.6.1"
kotlin = "1.9.24"
androidx-core = "1.13.1"
androidx-activity = "1.9.1"
androidx-annotation = "1.8.2"
androidx-biometric = "1.2.0-alpha05"
androidx-camera = "1.3.4"
androidx-camera = "1.5.3"
androidx-fragment = "1.8.2"
androidx-security-crypto = "1.1.0-alpha06"
androidx-datastore = "1.1.1"

Some files were not shown because too many files have changed in this diff Show More