mirror of
https://github.com/MatsuriDayo/nekoray.git
synced 2025-12-17 20:44:38 +03:00
upload code
This commit is contained in:
146
.github/workflows/build-qv2ray-cmake.yml
vendored
Normal file
146
.github/workflows/build-qv2ray-cmake.yml
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
name: Nekoray build matrix - cmake
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Release Tag'
|
||||
required: true
|
||||
publish:
|
||||
description: 'Publish: If want ignore'
|
||||
required: false
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ windows-2022, ubuntu-18.04 ]
|
||||
arch: [ x64 ]
|
||||
build_type: [ Release ]
|
||||
qt_version: [ 5.15.2 ]
|
||||
include:
|
||||
- platform: windows-2022
|
||||
arch: x64
|
||||
qtarch: win64_msvc2019_64
|
||||
fail-fast: false
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
env:
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
|
||||
|
||||
steps:
|
||||
- name: Checking out sources
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: "recursive"
|
||||
- name: Install MSVC compiler
|
||||
if: matrix.platform == 'windows-2022'
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
# 14.1 is for vs2017, 14.2 is vs2019, following the upstream vcpkg build from Qv2ray-deps repo
|
||||
toolset: 14.2
|
||||
arch: ${{ matrix.arch }}
|
||||
- name: Cache Qt
|
||||
id: cache-qt
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ runner.workspace }}/Qt
|
||||
key: QtCache-${{ matrix.platform }}-${{ matrix.arch }}-${{ matrix.qt_version }}
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v2.14.0
|
||||
with:
|
||||
version: ${{ matrix.qt_version }}
|
||||
py7zrversion: ' '
|
||||
aqtversion: ' '
|
||||
setup-python: false
|
||||
cached: ${{ steps.cache-qt.outputs.cache-hit }}
|
||||
# ========================================================================================================= Other install
|
||||
- name: Windows - ${{ matrix.arch }} - ${{ matrix.qt_version }} - Setup Ninja
|
||||
if: matrix.platform == 'windows-2022'
|
||||
uses: ashutoshvarma/setup-ninja@master
|
||||
with:
|
||||
# ninja version to download. Default: 1.10.0
|
||||
version: 1.10.0
|
||||
- name: Linux - ${{ matrix.arch }} - ${{ matrix.qt_version }} - Setup Ninja
|
||||
shell: bash
|
||||
if: matrix.platform == 'ubuntu-18.04'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ninja-build
|
||||
- name: Install Golang
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
stable: false
|
||||
go-version: 1.18.5
|
||||
# ========================================================================================================= 编译与 Qt 无关的依赖
|
||||
- name: Cache Download
|
||||
id: cache-deps
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: libs/deps
|
||||
key: ${{ hashFiles('libs/build*.sh') }}
|
||||
- name: Build Dependencies
|
||||
shell: bash
|
||||
if: steps.cache-deps.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
./libs/build_deps_all.sh
|
||||
# ========================================================================================================= Generate MakeFile and Build
|
||||
- name: Windows - ${{ matrix.qt_version }} - Generate MakeFile and Build
|
||||
shell: bash
|
||||
if: matrix.platform == 'windows-2022'
|
||||
env:
|
||||
CC: cl.exe
|
||||
CXX: cl.exe
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -GNinja \
|
||||
-DCMAKE_PREFIX_PATH=./libs/deps/built \
|
||||
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
|
||||
cmake --build . --parallel $(nproc)
|
||||
cd ..
|
||||
./libs/deploy_windows64.sh
|
||||
- name: Linux - ${{ matrix.qt_version }} - Generate MakeFile and Build
|
||||
shell: bash
|
||||
if: matrix.platform == 'ubuntu-18.04'
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -GNinja \
|
||||
-DCMAKE_PREFIX_PATH=./libs/deps/built \
|
||||
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
|
||||
cmake --build . --parallel $(nproc)
|
||||
cd ..
|
||||
./libs/deploy_linux64.sh
|
||||
# ========================================================================================================= Deployments
|
||||
- name: Uploading Artifact
|
||||
uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: NekoRay-${{ github.sha }}-${{ matrix.platform }}-${{ matrix.arch }}
|
||||
path: ./deployment/
|
||||
publish:
|
||||
name: Publish Release
|
||||
if: github.event.inputs.publish != 'y'
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Donwload Artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: NekoRay-${{ github.sha }}-ubuntu-18.04-x64
|
||||
path: artifacts-linux
|
||||
- name: Donwload Artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: NekoRay-${{ github.sha }}-windows-2022-x64
|
||||
path: artifacts-windows
|
||||
- name: Release
|
||||
run: |
|
||||
wget -O ghr.tar.gz https://github.com/tcnksm/ghr/releases/download/v0.13.0/ghr_v0.13.0_linux_amd64.tar.gz
|
||||
tar -xvf ghr.tar.gz
|
||||
mv ghr*linux_amd64/ghr .
|
||||
mkdir apks
|
||||
find artifacts-linux -name "*.tar.gz" -exec cp {} apks \;
|
||||
find artifacts-windows -name "*.zip" -exec cp {} apks \;
|
||||
./ghr -delete -t "${{ github.token }}" -n "${{ github.event.inputs.tag }}" "${{ github.event.inputs.tag }}" apks
|
||||
82
.gitignore
vendored
Normal file
82
.gitignore
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
# This file is used to ignore files which are generated
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
*~
|
||||
*.autosave
|
||||
*.a
|
||||
*.core
|
||||
*.moc
|
||||
*.o
|
||||
*.obj
|
||||
*.orig
|
||||
*.rej
|
||||
*.so
|
||||
*.so.*
|
||||
*_pch.h.cpp
|
||||
*_resource.rc
|
||||
.#*
|
||||
*.*#
|
||||
core
|
||||
!core/
|
||||
tags
|
||||
.DS_Store
|
||||
.directory
|
||||
*.debug
|
||||
Makefile*
|
||||
*.prl
|
||||
*.app
|
||||
moc_*.cpp
|
||||
ui_*.h
|
||||
qrc_*.cpp
|
||||
Thumbs.db
|
||||
*.res
|
||||
/.qmake.cache
|
||||
/.qmake.stash
|
||||
|
||||
# qtcreator generated files
|
||||
*.pro.user*
|
||||
|
||||
# xemacs temporary files
|
||||
*.flc
|
||||
|
||||
# Vim temporary files
|
||||
.*.swp
|
||||
|
||||
# Visual Studio generated files
|
||||
*.ib_pdb_index
|
||||
*.idb
|
||||
*.ilk
|
||||
*.pdb
|
||||
*.sln
|
||||
*.suo
|
||||
*.vcproj
|
||||
*vcproj.*.*.user
|
||||
*.ncb
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.vcxproj
|
||||
*vcxproj.*
|
||||
|
||||
# MinGW generated files
|
||||
*.Debug
|
||||
*.Release
|
||||
|
||||
# Python byte code
|
||||
*.pyc
|
||||
|
||||
# Binaries
|
||||
# --------
|
||||
*.dll
|
||||
*.exe
|
||||
|
||||
# Custom
|
||||
/nekoray/
|
||||
/build/
|
||||
CMakeLists.txt.user*
|
||||
/cmake-build-*
|
||||
/build-*
|
||||
.vscode
|
||||
.idea
|
||||
|
||||
# Deploy
|
||||
/deployment
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "3rdparty/QHotkey"]
|
||||
path = 3rdparty/QHotkey
|
||||
url = https://github.com/Skycoder42/QHotkey.git
|
||||
1
3rdparty/QHotkey
vendored
Submodule
1
3rdparty/QHotkey
vendored
Submodule
Submodule 3rdparty/QHotkey added at 52e25acf22
38
3rdparty/QThreadCreateThread.hpp
vendored
Normal file
38
3rdparty/QThreadCreateThread.hpp
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <QThread>
|
||||
|
||||
// FOR OLD QT
|
||||
|
||||
class QThreadCreateThread : public QThread {
|
||||
public:
|
||||
explicit QThreadCreateThread(std::future<void> &&future)
|
||||
: m_future(std::move(future)) {
|
||||
// deleteLater
|
||||
connect(this, &QThread::finished, this, &QThread::deleteLater);
|
||||
}
|
||||
|
||||
private:
|
||||
void run() override {
|
||||
m_future.get();
|
||||
}
|
||||
|
||||
std::future<void> m_future;
|
||||
};
|
||||
|
||||
inline QThread *createThreadImpl(std::future<void> &&future) {
|
||||
return new QThreadCreateThread(std::move(future));
|
||||
}
|
||||
|
||||
template<typename Function, typename... Args>
|
||||
QThread *createQThread(Function &&f, Args &&... args) {
|
||||
using DecayedFunction = typename std::decay<Function>::type;
|
||||
auto threadFunction =
|
||||
[f = static_cast<DecayedFunction>(std::forward<Function>(f))](auto &&... largs) mutable -> void {
|
||||
(void) std::invoke(std::move(f), std::forward<decltype(largs)>(largs)...);
|
||||
};
|
||||
|
||||
return createThreadImpl(std::async(std::launch::deferred,
|
||||
std::move(threadFunction),
|
||||
std::forward<Args>(args)...));
|
||||
}
|
||||
22
3rdparty/QtExtKeySequenceEdit.cpp
vendored
Normal file
22
3rdparty/QtExtKeySequenceEdit.cpp
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
#include "QtExtKeySequenceEdit.h"
|
||||
|
||||
QtExtKeySequenceEdit::QtExtKeySequenceEdit(QWidget *parent)
|
||||
: QKeySequenceEdit(parent) {
|
||||
}
|
||||
|
||||
QtExtKeySequenceEdit::~QtExtKeySequenceEdit() {
|
||||
}
|
||||
|
||||
void QtExtKeySequenceEdit::keyPressEvent(QKeyEvent *pEvent) {
|
||||
QKeySequenceEdit::keyPressEvent(pEvent);
|
||||
|
||||
QKeySequence keySeq = keySequence();
|
||||
if (keySeq.count() <= 0) {
|
||||
return;
|
||||
}
|
||||
int key = keySeq[0];
|
||||
if (key == Qt::Key_Backspace || key == Qt::Key_Delete) {
|
||||
key = 0;
|
||||
}
|
||||
setKeySequence(key);
|
||||
}
|
||||
11
3rdparty/QtExtKeySequenceEdit.h
vendored
Normal file
11
3rdparty/QtExtKeySequenceEdit.h
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
#include <QKeySequenceEdit>
|
||||
|
||||
class QtExtKeySequenceEdit : public QKeySequenceEdit {
|
||||
public:
|
||||
QtExtKeySequenceEdit(QWidget *parent);
|
||||
|
||||
~QtExtKeySequenceEdit();
|
||||
|
||||
protected:
|
||||
virtual void keyPressEvent(QKeyEvent *pEvent);
|
||||
};
|
||||
98
3rdparty/RunGuard.hpp
vendored
Normal file
98
3rdparty/RunGuard.hpp
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
#ifndef RUNGUARD_H
|
||||
#define RUNGUARD_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QSharedMemory>
|
||||
#include <QSystemSemaphore>
|
||||
#include <QCryptographicHash>
|
||||
|
||||
class RunGuard {
|
||||
|
||||
public:
|
||||
RunGuard(const QString &key);
|
||||
|
||||
~RunGuard();
|
||||
|
||||
bool isAnotherRunning();
|
||||
|
||||
bool tryToRun();
|
||||
|
||||
void release();
|
||||
|
||||
private:
|
||||
const QString key;
|
||||
const QString memLockKey;
|
||||
const QString sharedmemKey;
|
||||
|
||||
QSharedMemory sharedMem;
|
||||
QSystemSemaphore memLock;
|
||||
|
||||
Q_DISABLE_COPY(RunGuard)
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
QString generateKeyHash(const QString &key, const QString &salt) {
|
||||
QByteArray data;
|
||||
|
||||
data.append(key.toUtf8());
|
||||
data.append(salt.toUtf8());
|
||||
data = QCryptographicHash::hash(data, QCryptographicHash::Sha1).toHex();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
RunGuard::RunGuard(const QString &key)
|
||||
: key(key), memLockKey(generateKeyHash(key, "_memLockKey")),
|
||||
sharedmemKey(generateKeyHash(key, "_sharedmemKey")), sharedMem(sharedmemKey), memLock(memLockKey, 1) {
|
||||
memLock.acquire();
|
||||
{
|
||||
QSharedMemory fix(sharedmemKey); // Fix for *nix: http://habrahabr.ru/post/173281/
|
||||
fix.attach();
|
||||
}
|
||||
memLock.release();
|
||||
}
|
||||
|
||||
RunGuard::~RunGuard() {
|
||||
release();
|
||||
}
|
||||
|
||||
bool RunGuard::isAnotherRunning() {
|
||||
if (sharedMem.isAttached())
|
||||
return false;
|
||||
|
||||
memLock.acquire();
|
||||
const bool isRunning = sharedMem.attach();
|
||||
if (isRunning)
|
||||
sharedMem.detach();
|
||||
memLock.release();
|
||||
|
||||
return isRunning;
|
||||
}
|
||||
|
||||
bool RunGuard::tryToRun() {
|
||||
if (isAnotherRunning()) // Extra check
|
||||
return false;
|
||||
|
||||
memLock.acquire();
|
||||
const bool result = sharedMem.create(sizeof(quint64));
|
||||
memLock.release();
|
||||
if (!result) {
|
||||
release();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RunGuard::release() {
|
||||
memLock.acquire();
|
||||
if (sharedMem.isAttached())
|
||||
sharedMem.detach();
|
||||
memLock.release();
|
||||
}
|
||||
|
||||
#endif // RUNGUARD_H
|
||||
22
3rdparty/VT100Parser.hpp
vendored
Normal file
22
3rdparty/VT100Parser.hpp
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
inline QString cleanVT100String(const QString &in) {
|
||||
QString out;
|
||||
bool in_033 = false;
|
||||
for (auto &&chr: in) {
|
||||
if (chr == '\033') {
|
||||
in_033 = true;
|
||||
continue;
|
||||
}
|
||||
if (in_033) {
|
||||
if (chr == 'm') {
|
||||
in_033 = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
out += chr;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
114
3rdparty/WinCommander.cpp
vendored
Normal file
114
3rdparty/WinCommander.cpp
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2014 UpdateNode UG (haftungsbeschränkt)
|
||||
** Contact: code@updatenode.com
|
||||
**
|
||||
** This file is part of the UpdateNode Client.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial UpdateNode license may use this file
|
||||
** under the terms of the the Apache License, Version 2.0
|
||||
** Full license description file: LICENSE.COM
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation. Please review the following information to ensure the
|
||||
** GNU General Public License version 3.0 requirements will be met:
|
||||
** http://www.gnu.org/copyleft/gpl.html.
|
||||
** Full license description file: LICENSE.GPL
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "WinCommander.hpp"
|
||||
|
||||
#include <QSysInfo>
|
||||
#include <QDir>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
#include <sddl.h>
|
||||
#define MAX_KEY_LENGTH 255
|
||||
#define MAX_VALUE_NAME 16383
|
||||
#endif
|
||||
|
||||
|
||||
/*!
|
||||
Executes a command elevated specified by \apath , using paramters \aparameters.
|
||||
\n
|
||||
Parameter /aaWait decides if the function should return immediatelly after it's\n
|
||||
execution or wait for the exit of the launched process
|
||||
\n
|
||||
Returns the return value of the executed command
|
||||
*/
|
||||
uint WinCommander::runProcessElevated(const QString &path,
|
||||
const QStringList ¶meters,
|
||||
const QString &workingDir, bool aWait) {
|
||||
uint result = 0;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
QString params;
|
||||
HWND hwnd = NULL;
|
||||
LPCTSTR pszPath = (LPCTSTR)path.utf16();
|
||||
foreach(QString item, parameters)
|
||||
params += "\"" + item + "\" ";
|
||||
|
||||
LPCTSTR pszParameters = (LPCTSTR)params.utf16();
|
||||
QString dir;
|
||||
if (workingDir.isEmpty())
|
||||
dir = QDir::toNativeSeparators(QDir::currentPath());
|
||||
else
|
||||
dir = QDir::toNativeSeparators(workingDir);
|
||||
LPCTSTR pszDirectory = (LPCTSTR)dir.utf16();
|
||||
|
||||
SHELLEXECUTEINFO shex;
|
||||
DWORD dwCode = 0;
|
||||
|
||||
ZeroMemory(&shex, sizeof(shex));
|
||||
|
||||
shex.cbSize = sizeof(shex);
|
||||
shex.fMask = SEE_MASK_NOCLOSEPROCESS;
|
||||
shex.hwnd = hwnd;
|
||||
shex.lpVerb = TEXT("runas");
|
||||
shex.lpFile = pszPath;
|
||||
shex.lpParameters = pszParameters;
|
||||
shex.lpDirectory = pszDirectory;
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow
|
||||
shex.nShow = SW_SHOWMINIMIZED;
|
||||
|
||||
ShellExecuteEx(&shex);
|
||||
if (shex.hProcess)
|
||||
{
|
||||
if(aWait)
|
||||
{
|
||||
WaitForSingleObject(shex.hProcess, INFINITE );
|
||||
GetExitCodeProcess(shex.hProcess, &dwCode);
|
||||
}
|
||||
CloseHandle (shex.hProcess) ;
|
||||
}
|
||||
else
|
||||
return -1;
|
||||
|
||||
result = (uint)dwCode;
|
||||
#else
|
||||
Q_UNUSED(path);
|
||||
Q_UNUSED(parameters);
|
||||
Q_UNUSED(workingDir);
|
||||
Q_UNUSED(aWait);
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
Executes a command elevated specified by \apath , using paramters \aparameters.
|
||||
\n
|
||||
Parameter /aaWait decides if the function should return immediatelly after it's\n
|
||||
execution or wait for the exit of the launched process
|
||||
\n
|
||||
Returns the return value of the executed command
|
||||
*/
|
||||
uint WinCommander::runProcessElevated(const QString &path, const QString ¶meters, const QString &workingDir,
|
||||
bool aWait) {
|
||||
return runProcessElevated(path, QStringList() << parameters, workingDir, aWait);
|
||||
}
|
||||
42
3rdparty/WinCommander.hpp
vendored
Normal file
42
3rdparty/WinCommander.hpp
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2014 UpdateNode UG (haftungsbeschränkt)
|
||||
** Contact: code@updatenode.com
|
||||
**
|
||||
** This file is part of the UpdateNode Client.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial UpdateNode license may use this file
|
||||
** under the terms of the the Apache License, Version 2.0
|
||||
** Full license description file: LICENSE.COM
|
||||
**
|
||||
** GNU General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU
|
||||
** General Public License version 3.0 as published by the Free Software
|
||||
** Foundation. Please review the following information to ensure the
|
||||
** GNU General Public License version 3.0 requirements will be met:
|
||||
** http://www.gnu.org/copyleft/gpl.html.
|
||||
** Full license description file: LICENSE.GPL
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef WINCOMMANDER_H
|
||||
#define WINCOMMANDER_H
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
class WinCommander {
|
||||
public:
|
||||
static uint runProcessElevated(const QString &path,
|
||||
const QStringList ¶meters = QStringList(),
|
||||
const QString &workingDir = QString(),
|
||||
bool aWait = true);
|
||||
|
||||
static uint runProcessElevated(const QString &path,
|
||||
const QString ¶meters = QString(),
|
||||
const QString &workingDir = QString(),
|
||||
bool aWait = true);
|
||||
};
|
||||
|
||||
#endif // WINCOMMANDER_H
|
||||
386
3rdparty/ZxingQtReader.hpp
vendored
Normal file
386
3rdparty/ZxingQtReader.hpp
vendored
Normal file
@@ -0,0 +1,386 @@
|
||||
#pragma once
|
||||
/*
|
||||
* Copyright 2020 Axel Waggershauser
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "ZXing/ReadBarcode.h"
|
||||
|
||||
#include <QImage>
|
||||
#include <QDebug>
|
||||
#include <QMetaType>
|
||||
|
||||
#ifdef QT_MULTIMEDIA_LIB
|
||||
#include <QAbstractVideoFilter>
|
||||
#include <QElapsedTimer>
|
||||
#endif
|
||||
|
||||
// This is some sample code to start a discussion about how a minimal and header-only Qt wrapper/helper could look like.
|
||||
|
||||
namespace ZXingQt {
|
||||
|
||||
Q_NAMESPACE
|
||||
|
||||
//TODO: find a better way to export these enums to QML than to duplicate their definition
|
||||
// #ifdef Q_MOC_RUN produces meta information in the moc output but it does end up working in qml
|
||||
#ifdef QT_QML_LIB
|
||||
enum class BarcodeFormat
|
||||
{
|
||||
None = 0, ///< Used as a return value if no valid barcode has been detected
|
||||
Aztec = (1 << 0), ///< Aztec (2D)
|
||||
Codabar = (1 << 1), ///< Codabar (1D)
|
||||
Code39 = (1 << 2), ///< Code39 (1D)
|
||||
Code93 = (1 << 3), ///< Code93 (1D)
|
||||
Code128 = (1 << 4), ///< Code128 (1D)
|
||||
DataBar = (1 << 5), ///< GS1 DataBar, formerly known as RSS 14
|
||||
DataBarExpanded = (1 << 6), ///< GS1 DataBar Expanded, formerly known as RSS EXPANDED
|
||||
DataMatrix = (1 << 7), ///< DataMatrix (2D)
|
||||
EAN8 = (1 << 8), ///< EAN-8 (1D)
|
||||
EAN13 = (1 << 9), ///< EAN-13 (1D)
|
||||
ITF = (1 << 10), ///< ITF (Interleaved Two of Five) (1D)
|
||||
MaxiCode = (1 << 11), ///< MaxiCode (2D)
|
||||
PDF417 = (1 << 12), ///< PDF417 (1D) or (2D)
|
||||
QRCode = (1 << 13), ///< QR Code (2D)
|
||||
UPCA = (1 << 14), ///< UPC-A (1D)
|
||||
UPCE = (1 << 15), ///< UPC-E (1D)
|
||||
MicroQRCode = (1 << 16), ///< Micro QR Code (2D)
|
||||
|
||||
OneDCodes = Codabar | Code39 | Code93 | Code128 | EAN8 | EAN13 | ITF | DataBar | DataBarExpanded | UPCA | UPCE,
|
||||
TwoDCodes = Aztec | DataMatrix | MaxiCode | PDF417 | QRCode | MicroQRCode,
|
||||
};
|
||||
|
||||
enum class DecodeStatus
|
||||
{
|
||||
NoError = 0,
|
||||
NotFound,
|
||||
FormatError,
|
||||
ChecksumError,
|
||||
};
|
||||
#else
|
||||
using ZXing::BarcodeFormat;
|
||||
using ZXing::DecodeStatus;
|
||||
#endif
|
||||
|
||||
using ZXing::DecodeHints;
|
||||
using ZXing::Binarizer;
|
||||
using ZXing::BarcodeFormats;
|
||||
|
||||
Q_ENUM_NS(BarcodeFormat)
|
||||
Q_ENUM_NS(DecodeStatus)
|
||||
|
||||
template<typename T, typename = decltype(ZXing::ToString(T()))>
|
||||
QDebug operator<<(QDebug dbg, const T& v)
|
||||
{
|
||||
return dbg.noquote() << QString::fromStdString(ToString(v));
|
||||
}
|
||||
|
||||
class Position : public ZXing::Quadrilateral<QPoint>
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
Q_PROPERTY(QPoint topLeft READ topLeft)
|
||||
Q_PROPERTY(QPoint topRight READ topRight)
|
||||
Q_PROPERTY(QPoint bottomRight READ bottomRight)
|
||||
Q_PROPERTY(QPoint bottomLeft READ bottomLeft)
|
||||
|
||||
using Base = ZXing::Quadrilateral<QPoint>;
|
||||
|
||||
public:
|
||||
using Base::Base;
|
||||
};
|
||||
|
||||
class Result : private ZXing::Result
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
Q_PROPERTY(BarcodeFormat format READ format)
|
||||
Q_PROPERTY(QString formatName READ formatName)
|
||||
Q_PROPERTY(QString text READ text)
|
||||
Q_PROPERTY(QByteArray rawBytes READ rawBytes)
|
||||
Q_PROPERTY(bool isValid READ isValid)
|
||||
Q_PROPERTY(DecodeStatus status READ status)
|
||||
Q_PROPERTY(Position position READ position)
|
||||
|
||||
QString _text;
|
||||
QByteArray _rawBytes;
|
||||
Position _position;
|
||||
|
||||
public:
|
||||
Result() : ZXing::Result(ZXing::DecodeStatus::NotFound) {} // required for qmetatype machinery
|
||||
|
||||
explicit Result(ZXing::Result&& r) : ZXing::Result(std::move(r)) {
|
||||
_text = QString::fromWCharArray(ZXing::Result::text().c_str());
|
||||
_rawBytes = QByteArray(reinterpret_cast<const char*>(ZXing::Result::rawBytes().data()),
|
||||
Size(ZXing::Result::rawBytes()));
|
||||
auto& pos = ZXing::Result::position();
|
||||
auto qp = [&pos](int i) { return QPoint(pos[i].x, pos[i].y); };
|
||||
_position = {qp(0), qp(1), qp(2), qp(3)};
|
||||
}
|
||||
|
||||
using ZXing::Result::isValid;
|
||||
|
||||
BarcodeFormat format() const { return static_cast<BarcodeFormat>(ZXing::Result::format()); }
|
||||
DecodeStatus status() const { return static_cast<DecodeStatus>(ZXing::Result::status()); }
|
||||
QString formatName() const { return QString::fromStdString(ZXing::ToString(ZXing::Result::format())); }
|
||||
const QString& text() const { return _text; }
|
||||
const QByteArray& rawBytes() const { return _rawBytes; }
|
||||
const Position& position() const { return _position; }
|
||||
|
||||
// For debugging/development
|
||||
int runTime = 0;
|
||||
Q_PROPERTY(int runTime MEMBER runTime)
|
||||
};
|
||||
|
||||
inline Result ReadBarcode(const QImage& img, const DecodeHints& hints = {})
|
||||
{
|
||||
using namespace ZXing;
|
||||
|
||||
auto ImgFmtFromQImg = [](const QImage& img) {
|
||||
switch (img.format()) {
|
||||
case QImage::Format_ARGB32:
|
||||
case QImage::Format_RGB32:
|
||||
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
||||
return ImageFormat::BGRX;
|
||||
#else
|
||||
return ImageFormat::XRGB;
|
||||
#endif
|
||||
case QImage::Format_RGB888: return ImageFormat::RGB;
|
||||
case QImage::Format_RGBX8888:
|
||||
case QImage::Format_RGBA8888: return ImageFormat::RGBX;
|
||||
case QImage::Format_Grayscale8: return ImageFormat::Lum;
|
||||
default: return ImageFormat::None;
|
||||
}
|
||||
};
|
||||
|
||||
auto exec = [&](const QImage& img) {
|
||||
return Result(ZXing::ReadBarcode(
|
||||
{img.bits(), img.width(), img.height(), ImgFmtFromQImg(img), static_cast<int>(img.bytesPerLine())}, hints));
|
||||
};
|
||||
|
||||
return ImgFmtFromQImg(img) == ImageFormat::None ? exec(img.convertToFormat(QImage::Format_Grayscale8)) : exec(img);
|
||||
}
|
||||
|
||||
#ifdef QT_MULTIMEDIA_LIB
|
||||
inline Result ReadBarcode(const QVideoFrame& frame, const DecodeHints& hints = {})
|
||||
{
|
||||
using namespace ZXing;
|
||||
|
||||
auto img = frame; // shallow copy just get access to non-const map() function
|
||||
if (!frame.isValid() || !img.map(QAbstractVideoBuffer::ReadOnly)){
|
||||
qWarning() << "invalid QVideoFrame: could not map memory";
|
||||
return {};
|
||||
}
|
||||
//TODO c++17: SCOPE_EXIT([&] { img.unmap(); });
|
||||
|
||||
ImageFormat fmt = ImageFormat::None;
|
||||
int pixStride = 0;
|
||||
int pixOffset = 0;
|
||||
|
||||
switch (img.pixelFormat()) {
|
||||
case QVideoFrame::Format_ARGB32:
|
||||
case QVideoFrame::Format_ARGB32_Premultiplied:
|
||||
case QVideoFrame::Format_RGB32:
|
||||
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
||||
fmt = ImageFormat::BGRX;
|
||||
#else
|
||||
fmt = ImageFormat::XRGB;
|
||||
#endif
|
||||
break;
|
||||
|
||||
case QVideoFrame::Format_RGB24: fmt = ImageFormat::RGB; break;
|
||||
|
||||
case QVideoFrame::Format_BGRA32:
|
||||
case QVideoFrame::Format_BGRA32_Premultiplied:
|
||||
case QVideoFrame::Format_BGR32:
|
||||
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
||||
fmt = ImageFormat::RGBX;
|
||||
#else
|
||||
fmt = ImageFormat::XBGR;
|
||||
#endif
|
||||
break;
|
||||
|
||||
case QVideoFrame::Format_BGR24: fmt = ImageFormat::BGR; break;
|
||||
|
||||
case QVideoFrame::Format_AYUV444:
|
||||
case QVideoFrame::Format_AYUV444_Premultiplied:
|
||||
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
||||
fmt = ImageFormat::Lum, pixStride = 4, pixOffset = 3;
|
||||
#else
|
||||
fmt = ImageFormat::Lum, pixStride = 4, pixOffset = 2;
|
||||
#endif
|
||||
break;
|
||||
|
||||
case QVideoFrame::Format_YUV444: fmt = ImageFormat::Lum, pixStride = 3; break;
|
||||
case QVideoFrame::Format_YUV420P:
|
||||
case QVideoFrame::Format_NV12:
|
||||
case QVideoFrame::Format_NV21:
|
||||
case QVideoFrame::Format_IMC1:
|
||||
case QVideoFrame::Format_IMC2:
|
||||
case QVideoFrame::Format_IMC3:
|
||||
case QVideoFrame::Format_IMC4:
|
||||
case QVideoFrame::Format_YV12: fmt = ImageFormat::Lum; break;
|
||||
case QVideoFrame::Format_UYVY: fmt = ImageFormat::Lum, pixStride = 2, pixOffset = 1; break;
|
||||
case QVideoFrame::Format_YUYV: fmt = ImageFormat::Lum, pixStride = 2; break;
|
||||
|
||||
case QVideoFrame::Format_Y8: fmt = ImageFormat::Lum; break;
|
||||
case QVideoFrame::Format_Y16: fmt = ImageFormat::Lum, pixStride = 2, pixOffset = 1; break;
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
|
||||
case QVideoFrame::Format_ABGR32:
|
||||
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
||||
fmt = ImageFormat::RGBX;
|
||||
#else
|
||||
fmt = ImageFormat::XBGR;
|
||||
#endif
|
||||
break;
|
||||
#endif
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||
case QVideoFrame::Format_YUV422P: fmt = ImageFormat::Lum; break;
|
||||
#endif
|
||||
default: break;
|
||||
}
|
||||
|
||||
Result res;
|
||||
if (fmt != ImageFormat::None) {
|
||||
res = Result(
|
||||
ZXing::ReadBarcode({img.bits() + pixOffset, img.width(), img.height(), fmt, img.bytesPerLine(), pixStride},
|
||||
hints));
|
||||
} else {
|
||||
if (QVideoFrame::imageFormatFromPixelFormat(img.pixelFormat()) != QImage::Format_Invalid)
|
||||
res = ReadBarcode(img.image(), hints);
|
||||
}
|
||||
|
||||
img.unmap();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#define ZQ_PROPERTY(Type, name, setter) \
|
||||
public: \
|
||||
Q_PROPERTY(Type name READ name WRITE setter NOTIFY name##Changed) \
|
||||
Type name() const noexcept { return DecodeHints::name(); } \
|
||||
Q_SLOT void setter(const Type& newVal) \
|
||||
{ \
|
||||
if (name() != newVal) { \
|
||||
DecodeHints::setter(newVal); \
|
||||
emit name##Changed(); \
|
||||
} \
|
||||
} \
|
||||
Q_SIGNAL void name##Changed();
|
||||
|
||||
class VideoFilter : public QAbstractVideoFilter, private DecodeHints
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
VideoFilter(QObject* parent = nullptr) : QAbstractVideoFilter(parent) {}
|
||||
|
||||
QVideoFilterRunnable* createFilterRunnable() override;
|
||||
|
||||
// TODO: find out how to properly expose QFlags to QML
|
||||
// simply using ZQ_PROPERTY(BarcodeFormats, formats, setFormats)
|
||||
// results in the runtime error "can't assign int to formats"
|
||||
Q_PROPERTY(int formats READ formats WRITE setFormats NOTIFY formatsChanged)
|
||||
int formats() const noexcept
|
||||
{
|
||||
auto fmts = DecodeHints::formats();
|
||||
return *reinterpret_cast<int*>(&fmts);
|
||||
}
|
||||
Q_SLOT void setFormats(int newVal)
|
||||
{
|
||||
if (formats() != newVal) {
|
||||
DecodeHints::setFormats(static_cast<ZXing::BarcodeFormat>(newVal));
|
||||
emit formatsChanged();
|
||||
qDebug() << DecodeHints::formats();
|
||||
}
|
||||
}
|
||||
Q_SIGNAL void formatsChanged();
|
||||
|
||||
ZQ_PROPERTY(bool, tryRotate, setTryRotate)
|
||||
ZQ_PROPERTY(bool, tryHarder, setTryHarder)
|
||||
|
||||
public slots:
|
||||
Result process(const QVideoFrame& image)
|
||||
{
|
||||
QElapsedTimer t;
|
||||
t.start();
|
||||
|
||||
auto res = ReadBarcode(image, *this);
|
||||
|
||||
res.runTime = t.elapsed();
|
||||
|
||||
emit newResult(res);
|
||||
if (res.isValid())
|
||||
emit foundBarcode(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
signals:
|
||||
void newResult(Result result);
|
||||
void foundBarcode(Result result);
|
||||
};
|
||||
|
||||
#undef ZX_PROPERTY
|
||||
|
||||
class VideoFilterRunnable : public QVideoFilterRunnable
|
||||
{
|
||||
VideoFilter* _filter = nullptr;
|
||||
|
||||
public:
|
||||
explicit VideoFilterRunnable(VideoFilter* filter) : _filter(filter) {}
|
||||
|
||||
QVideoFrame run(QVideoFrame* input, const QVideoSurfaceFormat& /*surfaceFormat*/, RunFlags /*flags*/) override
|
||||
{
|
||||
_filter->process(*input);
|
||||
return *input;
|
||||
}
|
||||
};
|
||||
|
||||
inline QVideoFilterRunnable* VideoFilter::createFilterRunnable()
|
||||
{
|
||||
return new VideoFilterRunnable(this);
|
||||
}
|
||||
|
||||
#endif // QT_MULTIMEDIA_LIB
|
||||
|
||||
} // namespace ZXingQt
|
||||
|
||||
|
||||
Q_DECLARE_METATYPE(ZXingQt::Position)
|
||||
Q_DECLARE_METATYPE(ZXingQt::Result)
|
||||
|
||||
#ifdef QT_QML_LIB
|
||||
|
||||
#include <QQmlEngine>
|
||||
|
||||
namespace ZXingQt {
|
||||
|
||||
inline void registerQmlAndMetaTypes()
|
||||
{
|
||||
qRegisterMetaType<ZXingQt::BarcodeFormat>("BarcodeFormat");
|
||||
qRegisterMetaType<ZXingQt::DecodeStatus>("DecodeStatus");
|
||||
|
||||
// supposedly the Q_DECLARE_METATYPE should be used with the overload without a custom name
|
||||
// but then the qml side complains about "unregistered type"
|
||||
qRegisterMetaType<ZXingQt::Position>("Position");
|
||||
qRegisterMetaType<ZXingQt::Result>("Result");
|
||||
|
||||
qmlRegisterUncreatableMetaObject(
|
||||
ZXingQt::staticMetaObject, "ZXing", 1, 0, "ZXing", "Access to enums & flags only");
|
||||
qmlRegisterType<ZXingQt::VideoFilter>("ZXing", 1, 0, "VideoFilter");
|
||||
}
|
||||
|
||||
} // namespace ZXingQt
|
||||
|
||||
#endif // QT_QML_LIB
|
||||
830
3rdparty/qrcodegen.cpp
vendored
Normal file
830
3rdparty/qrcodegen.cpp
vendored
Normal file
@@ -0,0 +1,830 @@
|
||||
/*
|
||||
* QR Code generator library (C++)
|
||||
*
|
||||
* Copyright (c) Project Nayuki. (MIT License)
|
||||
* https://www.nayuki.io/page/qr-code-generator-library
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
* - The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
* - The Software is provided "as is", without warranty of any kind, express or
|
||||
* implied, including but not limited to the warranties of merchantability,
|
||||
* fitness for a particular purpose and noninfringement. In no event shall the
|
||||
* authors or copyright holders be liable for any claim, damages or other
|
||||
* liability, whether in an action of contract, tort or otherwise, arising from,
|
||||
* out of or in connection with the Software or the use or other dealings in the
|
||||
* Software.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
#include "qrcodegen.hpp"
|
||||
|
||||
using std::int8_t;
|
||||
using std::uint8_t;
|
||||
using std::size_t;
|
||||
using std::vector;
|
||||
|
||||
|
||||
namespace qrcodegen {
|
||||
|
||||
/*---- Class QrSegment ----*/
|
||||
|
||||
QrSegment::Mode::Mode(int mode, int cc0, int cc1, int cc2) :
|
||||
modeBits(mode) {
|
||||
numBitsCharCount[0] = cc0;
|
||||
numBitsCharCount[1] = cc1;
|
||||
numBitsCharCount[2] = cc2;
|
||||
}
|
||||
|
||||
|
||||
int QrSegment::Mode::getModeBits() const {
|
||||
return modeBits;
|
||||
}
|
||||
|
||||
|
||||
int QrSegment::Mode::numCharCountBits(int ver) const {
|
||||
return numBitsCharCount[(ver + 7) / 17];
|
||||
}
|
||||
|
||||
|
||||
const QrSegment::Mode QrSegment::Mode::NUMERIC (0x1, 10, 12, 14);
|
||||
const QrSegment::Mode QrSegment::Mode::ALPHANUMERIC(0x2, 9, 11, 13);
|
||||
const QrSegment::Mode QrSegment::Mode::BYTE (0x4, 8, 16, 16);
|
||||
const QrSegment::Mode QrSegment::Mode::KANJI (0x8, 8, 10, 12);
|
||||
const QrSegment::Mode QrSegment::Mode::ECI (0x7, 0, 0, 0);
|
||||
|
||||
|
||||
QrSegment QrSegment::makeBytes(const vector<uint8_t> &data) {
|
||||
if (data.size() > static_cast<unsigned int>(INT_MAX))
|
||||
throw std::length_error("Data too long");
|
||||
BitBuffer bb;
|
||||
for (uint8_t b : data)
|
||||
bb.appendBits(b, 8);
|
||||
return QrSegment(Mode::BYTE, static_cast<int>(data.size()), std::move(bb));
|
||||
}
|
||||
|
||||
|
||||
QrSegment QrSegment::makeNumeric(const char *digits) {
|
||||
BitBuffer bb;
|
||||
int accumData = 0;
|
||||
int accumCount = 0;
|
||||
int charCount = 0;
|
||||
for (; *digits != '\0'; digits++, charCount++) {
|
||||
char c = *digits;
|
||||
if (c < '0' || c > '9')
|
||||
throw std::domain_error("String contains non-numeric characters");
|
||||
accumData = accumData * 10 + (c - '0');
|
||||
accumCount++;
|
||||
if (accumCount == 3) {
|
||||
bb.appendBits(static_cast<uint32_t>(accumData), 10);
|
||||
accumData = 0;
|
||||
accumCount = 0;
|
||||
}
|
||||
}
|
||||
if (accumCount > 0) // 1 or 2 digits remaining
|
||||
bb.appendBits(static_cast<uint32_t>(accumData), accumCount * 3 + 1);
|
||||
return QrSegment(Mode::NUMERIC, charCount, std::move(bb));
|
||||
}
|
||||
|
||||
|
||||
QrSegment QrSegment::makeAlphanumeric(const char *text) {
|
||||
BitBuffer bb;
|
||||
int accumData = 0;
|
||||
int accumCount = 0;
|
||||
int charCount = 0;
|
||||
for (; *text != '\0'; text++, charCount++) {
|
||||
const char *temp = std::strchr(ALPHANUMERIC_CHARSET, *text);
|
||||
if (temp == nullptr)
|
||||
throw std::domain_error("String contains unencodable characters in alphanumeric mode");
|
||||
accumData = accumData * 45 + static_cast<int>(temp - ALPHANUMERIC_CHARSET);
|
||||
accumCount++;
|
||||
if (accumCount == 2) {
|
||||
bb.appendBits(static_cast<uint32_t>(accumData), 11);
|
||||
accumData = 0;
|
||||
accumCount = 0;
|
||||
}
|
||||
}
|
||||
if (accumCount > 0) // 1 character remaining
|
||||
bb.appendBits(static_cast<uint32_t>(accumData), 6);
|
||||
return QrSegment(Mode::ALPHANUMERIC, charCount, std::move(bb));
|
||||
}
|
||||
|
||||
|
||||
vector<QrSegment> QrSegment::makeSegments(const char *text) {
|
||||
// Select the most efficient segment encoding automatically
|
||||
vector<QrSegment> result;
|
||||
if (*text == '\0'); // Leave result empty
|
||||
else if (isNumeric(text))
|
||||
result.push_back(makeNumeric(text));
|
||||
else if (isAlphanumeric(text))
|
||||
result.push_back(makeAlphanumeric(text));
|
||||
else {
|
||||
vector<uint8_t> bytes;
|
||||
for (; *text != '\0'; text++)
|
||||
bytes.push_back(static_cast<uint8_t>(*text));
|
||||
result.push_back(makeBytes(bytes));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
QrSegment QrSegment::makeEci(long assignVal) {
|
||||
BitBuffer bb;
|
||||
if (assignVal < 0)
|
||||
throw std::domain_error("ECI assignment value out of range");
|
||||
else if (assignVal < (1 << 7))
|
||||
bb.appendBits(static_cast<uint32_t>(assignVal), 8);
|
||||
else if (assignVal < (1 << 14)) {
|
||||
bb.appendBits(2, 2);
|
||||
bb.appendBits(static_cast<uint32_t>(assignVal), 14);
|
||||
} else if (assignVal < 1000000L) {
|
||||
bb.appendBits(6, 3);
|
||||
bb.appendBits(static_cast<uint32_t>(assignVal), 21);
|
||||
} else
|
||||
throw std::domain_error("ECI assignment value out of range");
|
||||
return QrSegment(Mode::ECI, 0, std::move(bb));
|
||||
}
|
||||
|
||||
|
||||
QrSegment::QrSegment(const Mode &md, int numCh, const std::vector<bool> &dt) :
|
||||
mode(&md),
|
||||
numChars(numCh),
|
||||
data(dt) {
|
||||
if (numCh < 0)
|
||||
throw std::domain_error("Invalid value");
|
||||
}
|
||||
|
||||
|
||||
QrSegment::QrSegment(const Mode &md, int numCh, std::vector<bool> &&dt) :
|
||||
mode(&md),
|
||||
numChars(numCh),
|
||||
data(std::move(dt)) {
|
||||
if (numCh < 0)
|
||||
throw std::domain_error("Invalid value");
|
||||
}
|
||||
|
||||
|
||||
int QrSegment::getTotalBits(const vector<QrSegment> &segs, int version) {
|
||||
int result = 0;
|
||||
for (const QrSegment &seg : segs) {
|
||||
int ccbits = seg.mode->numCharCountBits(version);
|
||||
if (seg.numChars >= (1L << ccbits))
|
||||
return -1; // The segment's length doesn't fit the field's bit width
|
||||
if (4 + ccbits > INT_MAX - result)
|
||||
return -1; // The sum will overflow an int type
|
||||
result += 4 + ccbits;
|
||||
if (seg.data.size() > static_cast<unsigned int>(INT_MAX - result))
|
||||
return -1; // The sum will overflow an int type
|
||||
result += static_cast<int>(seg.data.size());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
bool QrSegment::isNumeric(const char *text) {
|
||||
for (; *text != '\0'; text++) {
|
||||
char c = *text;
|
||||
if (c < '0' || c > '9')
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool QrSegment::isAlphanumeric(const char *text) {
|
||||
for (; *text != '\0'; text++) {
|
||||
if (std::strchr(ALPHANUMERIC_CHARSET, *text) == nullptr)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
const QrSegment::Mode &QrSegment::getMode() const {
|
||||
return *mode;
|
||||
}
|
||||
|
||||
|
||||
int QrSegment::getNumChars() const {
|
||||
return numChars;
|
||||
}
|
||||
|
||||
|
||||
const std::vector<bool> &QrSegment::getData() const {
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
const char *QrSegment::ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
|
||||
|
||||
|
||||
|
||||
/*---- Class QrCode ----*/
|
||||
|
||||
int QrCode::getFormatBits(Ecc ecl) {
|
||||
switch (ecl) {
|
||||
case Ecc::LOW : return 1;
|
||||
case Ecc::MEDIUM : return 0;
|
||||
case Ecc::QUARTILE: return 3;
|
||||
case Ecc::HIGH : return 2;
|
||||
default: throw std::logic_error("Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QrCode QrCode::encodeText(const char *text, Ecc ecl) {
|
||||
vector<QrSegment> segs = QrSegment::makeSegments(text);
|
||||
return encodeSegments(segs, ecl);
|
||||
}
|
||||
|
||||
|
||||
QrCode QrCode::encodeBinary(const vector<uint8_t> &data, Ecc ecl) {
|
||||
vector<QrSegment> segs{QrSegment::makeBytes(data)};
|
||||
return encodeSegments(segs, ecl);
|
||||
}
|
||||
|
||||
|
||||
QrCode QrCode::encodeSegments(const vector<QrSegment> &segs, Ecc ecl,
|
||||
int minVersion, int maxVersion, int mask, bool boostEcl) {
|
||||
if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7)
|
||||
throw std::invalid_argument("Invalid value");
|
||||
|
||||
// Find the minimal version number to use
|
||||
int version, dataUsedBits;
|
||||
for (version = minVersion; ; version++) {
|
||||
int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available
|
||||
dataUsedBits = QrSegment::getTotalBits(segs, version);
|
||||
if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits)
|
||||
break; // This version number is found to be suitable
|
||||
if (version >= maxVersion) { // All versions in the range could not fit the given data
|
||||
std::ostringstream sb;
|
||||
if (dataUsedBits == -1)
|
||||
sb << "Segment too long";
|
||||
else {
|
||||
sb << "Data length = " << dataUsedBits << " bits, ";
|
||||
sb << "Max capacity = " << dataCapacityBits << " bits";
|
||||
}
|
||||
throw data_too_long(sb.str());
|
||||
}
|
||||
}
|
||||
assert(dataUsedBits != -1);
|
||||
|
||||
// Increase the error correction level while the data still fits in the current version number
|
||||
for (Ecc newEcl : {Ecc::MEDIUM, Ecc::QUARTILE, Ecc::HIGH}) { // From low to high
|
||||
if (boostEcl && dataUsedBits <= getNumDataCodewords(version, newEcl) * 8)
|
||||
ecl = newEcl;
|
||||
}
|
||||
|
||||
// Concatenate all segments to create the data bit string
|
||||
BitBuffer bb;
|
||||
for (const QrSegment &seg : segs) {
|
||||
bb.appendBits(static_cast<uint32_t>(seg.getMode().getModeBits()), 4);
|
||||
bb.appendBits(static_cast<uint32_t>(seg.getNumChars()), seg.getMode().numCharCountBits(version));
|
||||
bb.insert(bb.end(), seg.getData().begin(), seg.getData().end());
|
||||
}
|
||||
assert(bb.size() == static_cast<unsigned int>(dataUsedBits));
|
||||
|
||||
// Add terminator and pad up to a byte if applicable
|
||||
size_t dataCapacityBits = static_cast<size_t>(getNumDataCodewords(version, ecl)) * 8;
|
||||
assert(bb.size() <= dataCapacityBits);
|
||||
bb.appendBits(0, std::min(4, static_cast<int>(dataCapacityBits - bb.size())));
|
||||
bb.appendBits(0, (8 - static_cast<int>(bb.size() % 8)) % 8);
|
||||
assert(bb.size() % 8 == 0);
|
||||
|
||||
// Pad with alternating bytes until data capacity is reached
|
||||
for (uint8_t padByte = 0xEC; bb.size() < dataCapacityBits; padByte ^= 0xEC ^ 0x11)
|
||||
bb.appendBits(padByte, 8);
|
||||
|
||||
// Pack bits into bytes in big endian
|
||||
vector<uint8_t> dataCodewords(bb.size() / 8);
|
||||
for (size_t i = 0; i < bb.size(); i++)
|
||||
dataCodewords.at(i >> 3) |= (bb.at(i) ? 1 : 0) << (7 - (i & 7));
|
||||
|
||||
// Create the QR Code object
|
||||
return QrCode(version, ecl, dataCodewords, mask);
|
||||
}
|
||||
|
||||
|
||||
QrCode::QrCode(int ver, Ecc ecl, const vector<uint8_t> &dataCodewords, int msk) :
|
||||
// Initialize fields and check arguments
|
||||
version(ver),
|
||||
errorCorrectionLevel(ecl) {
|
||||
if (ver < MIN_VERSION || ver > MAX_VERSION)
|
||||
throw std::domain_error("Version value out of range");
|
||||
if (msk < -1 || msk > 7)
|
||||
throw std::domain_error("Mask value out of range");
|
||||
size = ver * 4 + 17;
|
||||
size_t sz = static_cast<size_t>(size);
|
||||
modules = vector<vector<bool> >(sz, vector<bool>(sz)); // Initially all light
|
||||
isFunction = vector<vector<bool> >(sz, vector<bool>(sz));
|
||||
|
||||
// Compute ECC, draw modules
|
||||
drawFunctionPatterns();
|
||||
const vector<uint8_t> allCodewords = addEccAndInterleave(dataCodewords);
|
||||
drawCodewords(allCodewords);
|
||||
|
||||
// Do masking
|
||||
if (msk == -1) { // Automatically choose best mask
|
||||
long minPenalty = LONG_MAX;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
applyMask(i);
|
||||
drawFormatBits(i);
|
||||
long penalty = getPenaltyScore();
|
||||
if (penalty < minPenalty) {
|
||||
msk = i;
|
||||
minPenalty = penalty;
|
||||
}
|
||||
applyMask(i); // Undoes the mask due to XOR
|
||||
}
|
||||
}
|
||||
assert(0 <= msk && msk <= 7);
|
||||
mask = msk;
|
||||
applyMask(msk); // Apply the final choice of mask
|
||||
drawFormatBits(msk); // Overwrite old format bits
|
||||
|
||||
isFunction.clear();
|
||||
isFunction.shrink_to_fit();
|
||||
}
|
||||
|
||||
|
||||
int QrCode::getVersion() const {
|
||||
return version;
|
||||
}
|
||||
|
||||
|
||||
int QrCode::getSize() const {
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
QrCode::Ecc QrCode::getErrorCorrectionLevel() const {
|
||||
return errorCorrectionLevel;
|
||||
}
|
||||
|
||||
|
||||
int QrCode::getMask() const {
|
||||
return mask;
|
||||
}
|
||||
|
||||
|
||||
bool QrCode::getModule(int x, int y) const {
|
||||
return 0 <= x && x < size && 0 <= y && y < size && module(x, y);
|
||||
}
|
||||
|
||||
|
||||
void QrCode::drawFunctionPatterns() {
|
||||
// Draw horizontal and vertical timing patterns
|
||||
for (int i = 0; i < size; i++) {
|
||||
setFunctionModule(6, i, i % 2 == 0);
|
||||
setFunctionModule(i, 6, i % 2 == 0);
|
||||
}
|
||||
|
||||
// Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules)
|
||||
drawFinderPattern(3, 3);
|
||||
drawFinderPattern(size - 4, 3);
|
||||
drawFinderPattern(3, size - 4);
|
||||
|
||||
// Draw numerous alignment patterns
|
||||
const vector<int> alignPatPos = getAlignmentPatternPositions();
|
||||
size_t numAlign = alignPatPos.size();
|
||||
for (size_t i = 0; i < numAlign; i++) {
|
||||
for (size_t j = 0; j < numAlign; j++) {
|
||||
// Don't draw on the three finder corners
|
||||
if (!((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0)))
|
||||
drawAlignmentPattern(alignPatPos.at(i), alignPatPos.at(j));
|
||||
}
|
||||
}
|
||||
|
||||
// Draw configuration data
|
||||
drawFormatBits(0); // Dummy mask value; overwritten later in the constructor
|
||||
drawVersion();
|
||||
}
|
||||
|
||||
|
||||
void QrCode::drawFormatBits(int msk) {
|
||||
// Calculate error correction code and pack bits
|
||||
int data = getFormatBits(errorCorrectionLevel) << 3 | msk; // errCorrLvl is uint2, msk is uint3
|
||||
int rem = data;
|
||||
for (int i = 0; i < 10; i++)
|
||||
rem = (rem << 1) ^ ((rem >> 9) * 0x537);
|
||||
int bits = (data << 10 | rem) ^ 0x5412; // uint15
|
||||
assert(bits >> 15 == 0);
|
||||
|
||||
// Draw first copy
|
||||
for (int i = 0; i <= 5; i++)
|
||||
setFunctionModule(8, i, getBit(bits, i));
|
||||
setFunctionModule(8, 7, getBit(bits, 6));
|
||||
setFunctionModule(8, 8, getBit(bits, 7));
|
||||
setFunctionModule(7, 8, getBit(bits, 8));
|
||||
for (int i = 9; i < 15; i++)
|
||||
setFunctionModule(14 - i, 8, getBit(bits, i));
|
||||
|
||||
// Draw second copy
|
||||
for (int i = 0; i < 8; i++)
|
||||
setFunctionModule(size - 1 - i, 8, getBit(bits, i));
|
||||
for (int i = 8; i < 15; i++)
|
||||
setFunctionModule(8, size - 15 + i, getBit(bits, i));
|
||||
setFunctionModule(8, size - 8, true); // Always dark
|
||||
}
|
||||
|
||||
|
||||
void QrCode::drawVersion() {
|
||||
if (version < 7)
|
||||
return;
|
||||
|
||||
// Calculate error correction code and pack bits
|
||||
int rem = version; // version is uint6, in the range [7, 40]
|
||||
for (int i = 0; i < 12; i++)
|
||||
rem = (rem << 1) ^ ((rem >> 11) * 0x1F25);
|
||||
long bits = static_cast<long>(version) << 12 | rem; // uint18
|
||||
assert(bits >> 18 == 0);
|
||||
|
||||
// Draw two copies
|
||||
for (int i = 0; i < 18; i++) {
|
||||
bool bit = getBit(bits, i);
|
||||
int a = size - 11 + i % 3;
|
||||
int b = i / 3;
|
||||
setFunctionModule(a, b, bit);
|
||||
setFunctionModule(b, a, bit);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void QrCode::drawFinderPattern(int x, int y) {
|
||||
for (int dy = -4; dy <= 4; dy++) {
|
||||
for (int dx = -4; dx <= 4; dx++) {
|
||||
int dist = std::max(std::abs(dx), std::abs(dy)); // Chebyshev/infinity norm
|
||||
int xx = x + dx, yy = y + dy;
|
||||
if (0 <= xx && xx < size && 0 <= yy && yy < size)
|
||||
setFunctionModule(xx, yy, dist != 2 && dist != 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void QrCode::drawAlignmentPattern(int x, int y) {
|
||||
for (int dy = -2; dy <= 2; dy++) {
|
||||
for (int dx = -2; dx <= 2; dx++)
|
||||
setFunctionModule(x + dx, y + dy, std::max(std::abs(dx), std::abs(dy)) != 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void QrCode::setFunctionModule(int x, int y, bool isDark) {
|
||||
size_t ux = static_cast<size_t>(x);
|
||||
size_t uy = static_cast<size_t>(y);
|
||||
modules .at(uy).at(ux) = isDark;
|
||||
isFunction.at(uy).at(ux) = true;
|
||||
}
|
||||
|
||||
|
||||
bool QrCode::module(int x, int y) const {
|
||||
return modules.at(static_cast<size_t>(y)).at(static_cast<size_t>(x));
|
||||
}
|
||||
|
||||
|
||||
vector<uint8_t> QrCode::addEccAndInterleave(const vector<uint8_t> &data) const {
|
||||
if (data.size() != static_cast<unsigned int>(getNumDataCodewords(version, errorCorrectionLevel)))
|
||||
throw std::invalid_argument("Invalid argument");
|
||||
|
||||
// Calculate parameter numbers
|
||||
int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[static_cast<int>(errorCorrectionLevel)][version];
|
||||
int blockEccLen = ECC_CODEWORDS_PER_BLOCK [static_cast<int>(errorCorrectionLevel)][version];
|
||||
int rawCodewords = getNumRawDataModules(version) / 8;
|
||||
int numShortBlocks = numBlocks - rawCodewords % numBlocks;
|
||||
int shortBlockLen = rawCodewords / numBlocks;
|
||||
|
||||
// Split data into blocks and append ECC to each block
|
||||
vector<vector<uint8_t> > blocks;
|
||||
const vector<uint8_t> rsDiv = reedSolomonComputeDivisor(blockEccLen);
|
||||
for (int i = 0, k = 0; i < numBlocks; i++) {
|
||||
vector<uint8_t> dat(data.cbegin() + k, data.cbegin() + (k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1)));
|
||||
k += static_cast<int>(dat.size());
|
||||
const vector<uint8_t> ecc = reedSolomonComputeRemainder(dat, rsDiv);
|
||||
if (i < numShortBlocks)
|
||||
dat.push_back(0);
|
||||
dat.insert(dat.end(), ecc.cbegin(), ecc.cend());
|
||||
blocks.push_back(std::move(dat));
|
||||
}
|
||||
|
||||
// Interleave (not concatenate) the bytes from every block into a single sequence
|
||||
vector<uint8_t> result;
|
||||
for (size_t i = 0; i < blocks.at(0).size(); i++) {
|
||||
for (size_t j = 0; j < blocks.size(); j++) {
|
||||
// Skip the padding byte in short blocks
|
||||
if (i != static_cast<unsigned int>(shortBlockLen - blockEccLen) || j >= static_cast<unsigned int>(numShortBlocks))
|
||||
result.push_back(blocks.at(j).at(i));
|
||||
}
|
||||
}
|
||||
assert(result.size() == static_cast<unsigned int>(rawCodewords));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
void QrCode::drawCodewords(const vector<uint8_t> &data) {
|
||||
if (data.size() != static_cast<unsigned int>(getNumRawDataModules(version) / 8))
|
||||
throw std::invalid_argument("Invalid argument");
|
||||
|
||||
size_t i = 0; // Bit index into the data
|
||||
// Do the funny zigzag scan
|
||||
for (int right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair
|
||||
if (right == 6)
|
||||
right = 5;
|
||||
for (int vert = 0; vert < size; vert++) { // Vertical counter
|
||||
for (int j = 0; j < 2; j++) {
|
||||
size_t x = static_cast<size_t>(right - j); // Actual x coordinate
|
||||
bool upward = ((right + 1) & 2) == 0;
|
||||
size_t y = static_cast<size_t>(upward ? size - 1 - vert : vert); // Actual y coordinate
|
||||
if (!isFunction.at(y).at(x) && i < data.size() * 8) {
|
||||
modules.at(y).at(x) = getBit(data.at(i >> 3), 7 - static_cast<int>(i & 7));
|
||||
i++;
|
||||
}
|
||||
// If this QR Code has any remainder bits (0 to 7), they were assigned as
|
||||
// 0/false/light by the constructor and are left unchanged by this method
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(i == data.size() * 8);
|
||||
}
|
||||
|
||||
|
||||
void QrCode::applyMask(int msk) {
|
||||
if (msk < 0 || msk > 7)
|
||||
throw std::domain_error("Mask value out of range");
|
||||
size_t sz = static_cast<size_t>(size);
|
||||
for (size_t y = 0; y < sz; y++) {
|
||||
for (size_t x = 0; x < sz; x++) {
|
||||
bool invert;
|
||||
switch (msk) {
|
||||
case 0: invert = (x + y) % 2 == 0; break;
|
||||
case 1: invert = y % 2 == 0; break;
|
||||
case 2: invert = x % 3 == 0; break;
|
||||
case 3: invert = (x + y) % 3 == 0; break;
|
||||
case 4: invert = (x / 3 + y / 2) % 2 == 0; break;
|
||||
case 5: invert = x * y % 2 + x * y % 3 == 0; break;
|
||||
case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break;
|
||||
case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break;
|
||||
default: throw std::logic_error("Unreachable");
|
||||
}
|
||||
modules.at(y).at(x) = modules.at(y).at(x) ^ (invert & !isFunction.at(y).at(x));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
long QrCode::getPenaltyScore() const {
|
||||
long result = 0;
|
||||
|
||||
// Adjacent modules in row having same color, and finder-like patterns
|
||||
for (int y = 0; y < size; y++) {
|
||||
bool runColor = false;
|
||||
int runX = 0;
|
||||
std::array<int,7> runHistory = {};
|
||||
for (int x = 0; x < size; x++) {
|
||||
if (module(x, y) == runColor) {
|
||||
runX++;
|
||||
if (runX == 5)
|
||||
result += PENALTY_N1;
|
||||
else if (runX > 5)
|
||||
result++;
|
||||
} else {
|
||||
finderPenaltyAddHistory(runX, runHistory);
|
||||
if (!runColor)
|
||||
result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3;
|
||||
runColor = module(x, y);
|
||||
runX = 1;
|
||||
}
|
||||
}
|
||||
result += finderPenaltyTerminateAndCount(runColor, runX, runHistory) * PENALTY_N3;
|
||||
}
|
||||
// Adjacent modules in column having same color, and finder-like patterns
|
||||
for (int x = 0; x < size; x++) {
|
||||
bool runColor = false;
|
||||
int runY = 0;
|
||||
std::array<int,7> runHistory = {};
|
||||
for (int y = 0; y < size; y++) {
|
||||
if (module(x, y) == runColor) {
|
||||
runY++;
|
||||
if (runY == 5)
|
||||
result += PENALTY_N1;
|
||||
else if (runY > 5)
|
||||
result++;
|
||||
} else {
|
||||
finderPenaltyAddHistory(runY, runHistory);
|
||||
if (!runColor)
|
||||
result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3;
|
||||
runColor = module(x, y);
|
||||
runY = 1;
|
||||
}
|
||||
}
|
||||
result += finderPenaltyTerminateAndCount(runColor, runY, runHistory) * PENALTY_N3;
|
||||
}
|
||||
|
||||
// 2*2 blocks of modules having same color
|
||||
for (int y = 0; y < size - 1; y++) {
|
||||
for (int x = 0; x < size - 1; x++) {
|
||||
bool color = module(x, y);
|
||||
if ( color == module(x + 1, y) &&
|
||||
color == module(x, y + 1) &&
|
||||
color == module(x + 1, y + 1))
|
||||
result += PENALTY_N2;
|
||||
}
|
||||
}
|
||||
|
||||
// Balance of dark and light modules
|
||||
int dark = 0;
|
||||
for (const vector<bool> &row : modules) {
|
||||
for (bool color : row) {
|
||||
if (color)
|
||||
dark++;
|
||||
}
|
||||
}
|
||||
int total = size * size; // Note that size is odd, so dark/total != 1/2
|
||||
// Compute the smallest integer k >= 0 such that (45-5k)% <= dark/total <= (55+5k)%
|
||||
int k = static_cast<int>((std::abs(dark * 20L - total * 10L) + total - 1) / total) - 1;
|
||||
assert(0 <= k && k <= 9);
|
||||
result += k * PENALTY_N4;
|
||||
assert(0 <= result && result <= 2568888L); // Non-tight upper bound based on default values of PENALTY_N1, ..., N4
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
vector<int> QrCode::getAlignmentPatternPositions() const {
|
||||
if (version == 1)
|
||||
return vector<int>();
|
||||
else {
|
||||
int numAlign = version / 7 + 2;
|
||||
int step = (version == 32) ? 26 :
|
||||
(version * 4 + numAlign * 2 + 1) / (numAlign * 2 - 2) * 2;
|
||||
vector<int> result;
|
||||
for (int i = 0, pos = size - 7; i < numAlign - 1; i++, pos -= step)
|
||||
result.insert(result.begin(), pos);
|
||||
result.insert(result.begin(), 6);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int QrCode::getNumRawDataModules(int ver) {
|
||||
if (ver < MIN_VERSION || ver > MAX_VERSION)
|
||||
throw std::domain_error("Version number out of range");
|
||||
int result = (16 * ver + 128) * ver + 64;
|
||||
if (ver >= 2) {
|
||||
int numAlign = ver / 7 + 2;
|
||||
result -= (25 * numAlign - 10) * numAlign - 55;
|
||||
if (ver >= 7)
|
||||
result -= 36;
|
||||
}
|
||||
assert(208 <= result && result <= 29648);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
int QrCode::getNumDataCodewords(int ver, Ecc ecl) {
|
||||
return getNumRawDataModules(ver) / 8
|
||||
- ECC_CODEWORDS_PER_BLOCK [static_cast<int>(ecl)][ver]
|
||||
* NUM_ERROR_CORRECTION_BLOCKS[static_cast<int>(ecl)][ver];
|
||||
}
|
||||
|
||||
|
||||
vector<uint8_t> QrCode::reedSolomonComputeDivisor(int degree) {
|
||||
if (degree < 1 || degree > 255)
|
||||
throw std::domain_error("Degree out of range");
|
||||
// Polynomial coefficients are stored from highest to lowest power, excluding the leading term which is always 1.
|
||||
// For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}.
|
||||
vector<uint8_t> result(static_cast<size_t>(degree));
|
||||
result.at(result.size() - 1) = 1; // Start off with the monomial x^0
|
||||
|
||||
// Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}),
|
||||
// and drop the highest monomial term which is always 1x^degree.
|
||||
// Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D).
|
||||
uint8_t root = 1;
|
||||
for (int i = 0; i < degree; i++) {
|
||||
// Multiply the current product by (x - r^i)
|
||||
for (size_t j = 0; j < result.size(); j++) {
|
||||
result.at(j) = reedSolomonMultiply(result.at(j), root);
|
||||
if (j + 1 < result.size())
|
||||
result.at(j) ^= result.at(j + 1);
|
||||
}
|
||||
root = reedSolomonMultiply(root, 0x02);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
vector<uint8_t> QrCode::reedSolomonComputeRemainder(const vector<uint8_t> &data, const vector<uint8_t> &divisor) {
|
||||
vector<uint8_t> result(divisor.size());
|
||||
for (uint8_t b : data) { // Polynomial division
|
||||
uint8_t factor = b ^ result.at(0);
|
||||
result.erase(result.begin());
|
||||
result.push_back(0);
|
||||
for (size_t i = 0; i < result.size(); i++)
|
||||
result.at(i) ^= reedSolomonMultiply(divisor.at(i), factor);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
uint8_t QrCode::reedSolomonMultiply(uint8_t x, uint8_t y) {
|
||||
// Russian peasant multiplication
|
||||
int z = 0;
|
||||
for (int i = 7; i >= 0; i--) {
|
||||
z = (z << 1) ^ ((z >> 7) * 0x11D);
|
||||
z ^= ((y >> i) & 1) * x;
|
||||
}
|
||||
assert(z >> 8 == 0);
|
||||
return static_cast<uint8_t>(z);
|
||||
}
|
||||
|
||||
|
||||
int QrCode::finderPenaltyCountPatterns(const std::array<int,7> &runHistory) const {
|
||||
int n = runHistory.at(1);
|
||||
assert(n <= size * 3);
|
||||
bool core = n > 0 && runHistory.at(2) == n && runHistory.at(3) == n * 3 && runHistory.at(4) == n && runHistory.at(5) == n;
|
||||
return (core && runHistory.at(0) >= n * 4 && runHistory.at(6) >= n ? 1 : 0)
|
||||
+ (core && runHistory.at(6) >= n * 4 && runHistory.at(0) >= n ? 1 : 0);
|
||||
}
|
||||
|
||||
|
||||
int QrCode::finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, std::array<int,7> &runHistory) const {
|
||||
if (currentRunColor) { // Terminate dark run
|
||||
finderPenaltyAddHistory(currentRunLength, runHistory);
|
||||
currentRunLength = 0;
|
||||
}
|
||||
currentRunLength += size; // Add light border to final run
|
||||
finderPenaltyAddHistory(currentRunLength, runHistory);
|
||||
return finderPenaltyCountPatterns(runHistory);
|
||||
}
|
||||
|
||||
|
||||
void QrCode::finderPenaltyAddHistory(int currentRunLength, std::array<int,7> &runHistory) const {
|
||||
if (runHistory.at(0) == 0)
|
||||
currentRunLength += size; // Add light border to initial run
|
||||
std::copy_backward(runHistory.cbegin(), runHistory.cend() - 1, runHistory.end());
|
||||
runHistory.at(0) = currentRunLength;
|
||||
}
|
||||
|
||||
|
||||
bool QrCode::getBit(long x, int i) {
|
||||
return ((x >> i) & 1) != 0;
|
||||
}
|
||||
|
||||
|
||||
/*---- Tables of constants ----*/
|
||||
|
||||
const int QrCode::PENALTY_N1 = 3;
|
||||
const int QrCode::PENALTY_N2 = 3;
|
||||
const int QrCode::PENALTY_N3 = 40;
|
||||
const int QrCode::PENALTY_N4 = 10;
|
||||
|
||||
|
||||
const int8_t QrCode::ECC_CODEWORDS_PER_BLOCK[4][41] = {
|
||||
// Version: (note that index 0 is for padding, and is set to an illegal value)
|
||||
//0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
|
||||
{-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low
|
||||
{-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium
|
||||
{-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile
|
||||
{-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High
|
||||
};
|
||||
|
||||
const int8_t QrCode::NUM_ERROR_CORRECTION_BLOCKS[4][41] = {
|
||||
// Version: (note that index 0 is for padding, and is set to an illegal value)
|
||||
//0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level
|
||||
{-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low
|
||||
{-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium
|
||||
{-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile
|
||||
{-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High
|
||||
};
|
||||
|
||||
|
||||
data_too_long::data_too_long(const std::string &msg) :
|
||||
std::length_error(msg) {}
|
||||
|
||||
|
||||
|
||||
/*---- Class BitBuffer ----*/
|
||||
|
||||
BitBuffer::BitBuffer()
|
||||
: std::vector<bool>() {}
|
||||
|
||||
|
||||
void BitBuffer::appendBits(std::uint32_t val, int len) {
|
||||
if (len < 0 || len > 31 || val >> len != 0)
|
||||
throw std::domain_error("Value out of range");
|
||||
for (int i = len - 1; i >= 0; i--) // Append bit by bit
|
||||
this->push_back(((val >> i) & 1) != 0);
|
||||
}
|
||||
|
||||
}
|
||||
549
3rdparty/qrcodegen.hpp
vendored
Normal file
549
3rdparty/qrcodegen.hpp
vendored
Normal file
@@ -0,0 +1,549 @@
|
||||
/*
|
||||
* QR Code generator library (C++)
|
||||
*
|
||||
* Copyright (c) Project Nayuki. (MIT License)
|
||||
* https://www.nayuki.io/page/qr-code-generator-library
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
* - The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
* - The Software is provided "as is", without warranty of any kind, express or
|
||||
* implied, including but not limited to the warranties of merchantability,
|
||||
* fitness for a particular purpose and noninfringement. In no event shall the
|
||||
* authors or copyright holders be liable for any claim, damages or other
|
||||
* liability, whether in an action of contract, tort or otherwise, arising from,
|
||||
* out of or in connection with the Software or the use or other dealings in the
|
||||
* Software.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace qrcodegen {
|
||||
|
||||
/*
|
||||
* A segment of character/binary/control data in a QR Code symbol.
|
||||
* Instances of this class are immutable.
|
||||
* The mid-level way to create a segment is to take the payload data
|
||||
* and call a static factory function such as QrSegment::makeNumeric().
|
||||
* The low-level way to create a segment is to custom-make the bit buffer
|
||||
* and call the QrSegment() constructor with appropriate values.
|
||||
* This segment class imposes no length restrictions, but QR Codes have restrictions.
|
||||
* Even in the most favorable conditions, a QR Code can only hold 7089 characters of data.
|
||||
* Any segment longer than this is meaningless for the purpose of generating QR Codes.
|
||||
*/
|
||||
class QrSegment final {
|
||||
|
||||
/*---- Public helper enumeration ----*/
|
||||
|
||||
/*
|
||||
* Describes how a segment's data bits are interpreted. Immutable.
|
||||
*/
|
||||
public: class Mode final {
|
||||
|
||||
/*-- Constants --*/
|
||||
|
||||
public: static const Mode NUMERIC;
|
||||
public: static const Mode ALPHANUMERIC;
|
||||
public: static const Mode BYTE;
|
||||
public: static const Mode KANJI;
|
||||
public: static const Mode ECI;
|
||||
|
||||
|
||||
/*-- Fields --*/
|
||||
|
||||
// The mode indicator bits, which is a uint4 value (range 0 to 15).
|
||||
private: int modeBits;
|
||||
|
||||
// Number of character count bits for three different version ranges.
|
||||
private: int numBitsCharCount[3];
|
||||
|
||||
|
||||
/*-- Constructor --*/
|
||||
|
||||
private: Mode(int mode, int cc0, int cc1, int cc2);
|
||||
|
||||
|
||||
/*-- Methods --*/
|
||||
|
||||
/*
|
||||
* (Package-private) Returns the mode indicator bits, which is an unsigned 4-bit value (range 0 to 15).
|
||||
*/
|
||||
public: int getModeBits() const;
|
||||
|
||||
/*
|
||||
* (Package-private) Returns the bit width of the character count field for a segment in
|
||||
* this mode in a QR Code at the given version number. The result is in the range [0, 16].
|
||||
*/
|
||||
public: int numCharCountBits(int ver) const;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*---- Static factory functions (mid level) ----*/
|
||||
|
||||
/*
|
||||
* Returns a segment representing the given binary data encoded in
|
||||
* byte mode. All input byte vectors are acceptable. Any text string
|
||||
* can be converted to UTF-8 bytes and encoded as a byte mode segment.
|
||||
*/
|
||||
public: static QrSegment makeBytes(const std::vector<std::uint8_t> &data);
|
||||
|
||||
|
||||
/*
|
||||
* Returns a segment representing the given string of decimal digits encoded in numeric mode.
|
||||
*/
|
||||
public: static QrSegment makeNumeric(const char *digits);
|
||||
|
||||
|
||||
/*
|
||||
* Returns a segment representing the given text string encoded in alphanumeric mode.
|
||||
* The characters allowed are: 0 to 9, A to Z (uppercase only), space,
|
||||
* dollar, percent, asterisk, plus, hyphen, period, slash, colon.
|
||||
*/
|
||||
public: static QrSegment makeAlphanumeric(const char *text);
|
||||
|
||||
|
||||
/*
|
||||
* Returns a list of zero or more segments to represent the given text string. The result
|
||||
* may use various segment modes and switch modes to optimize the length of the bit stream.
|
||||
*/
|
||||
public: static std::vector<QrSegment> makeSegments(const char *text);
|
||||
|
||||
|
||||
/*
|
||||
* Returns a segment representing an Extended Channel Interpretation
|
||||
* (ECI) designator with the given assignment value.
|
||||
*/
|
||||
public: static QrSegment makeEci(long assignVal);
|
||||
|
||||
|
||||
/*---- Public static helper functions ----*/
|
||||
|
||||
/*
|
||||
* Tests whether the given string can be encoded as a segment in numeric mode.
|
||||
* A string is encodable iff each character is in the range 0 to 9.
|
||||
*/
|
||||
public: static bool isNumeric(const char *text);
|
||||
|
||||
|
||||
/*
|
||||
* Tests whether the given string can be encoded as a segment in alphanumeric mode.
|
||||
* A string is encodable iff each character is in the following set: 0 to 9, A to Z
|
||||
* (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon.
|
||||
*/
|
||||
public: static bool isAlphanumeric(const char *text);
|
||||
|
||||
|
||||
|
||||
/*---- Instance fields ----*/
|
||||
|
||||
/* The mode indicator of this segment. Accessed through getMode(). */
|
||||
private: const Mode *mode;
|
||||
|
||||
/* The length of this segment's unencoded data. Measured in characters for
|
||||
* numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode.
|
||||
* Always zero or positive. Not the same as the data's bit length.
|
||||
* Accessed through getNumChars(). */
|
||||
private: int numChars;
|
||||
|
||||
/* The data bits of this segment. Accessed through getData(). */
|
||||
private: std::vector<bool> data;
|
||||
|
||||
|
||||
/*---- Constructors (low level) ----*/
|
||||
|
||||
/*
|
||||
* Creates a new QR Code segment with the given attributes and data.
|
||||
* The character count (numCh) must agree with the mode and the bit buffer length,
|
||||
* but the constraint isn't checked. The given bit buffer is copied and stored.
|
||||
*/
|
||||
public: QrSegment(const Mode &md, int numCh, const std::vector<bool> &dt);
|
||||
|
||||
|
||||
/*
|
||||
* Creates a new QR Code segment with the given parameters and data.
|
||||
* The character count (numCh) must agree with the mode and the bit buffer length,
|
||||
* but the constraint isn't checked. The given bit buffer is moved and stored.
|
||||
*/
|
||||
public: QrSegment(const Mode &md, int numCh, std::vector<bool> &&dt);
|
||||
|
||||
|
||||
/*---- Methods ----*/
|
||||
|
||||
/*
|
||||
* Returns the mode field of this segment.
|
||||
*/
|
||||
public: const Mode &getMode() const;
|
||||
|
||||
|
||||
/*
|
||||
* Returns the character count field of this segment.
|
||||
*/
|
||||
public: int getNumChars() const;
|
||||
|
||||
|
||||
/*
|
||||
* Returns the data bits of this segment.
|
||||
*/
|
||||
public: const std::vector<bool> &getData() const;
|
||||
|
||||
|
||||
// (Package-private) Calculates the number of bits needed to encode the given segments at
|
||||
// the given version. Returns a non-negative number if successful. Otherwise returns -1 if a
|
||||
// segment has too many characters to fit its length field, or the total bits exceeds INT_MAX.
|
||||
public: static int getTotalBits(const std::vector<QrSegment> &segs, int version);
|
||||
|
||||
|
||||
/*---- Private constant ----*/
|
||||
|
||||
/* The set of all legal characters in alphanumeric mode, where
|
||||
* each character value maps to the index in the string. */
|
||||
private: static const char *ALPHANUMERIC_CHARSET;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* A QR Code symbol, which is a type of two-dimension barcode.
|
||||
* Invented by Denso Wave and described in the ISO/IEC 18004 standard.
|
||||
* Instances of this class represent an immutable square grid of dark and light cells.
|
||||
* The class provides static factory functions to create a QR Code from text or binary data.
|
||||
* The class covers the QR Code Model 2 specification, supporting all versions (sizes)
|
||||
* from 1 to 40, all 4 error correction levels, and 4 character encoding modes.
|
||||
*
|
||||
* Ways to create a QR Code object:
|
||||
* - High level: Take the payload data and call QrCode::encodeText() or QrCode::encodeBinary().
|
||||
* - Mid level: Custom-make the list of segments and call QrCode::encodeSegments().
|
||||
* - Low level: Custom-make the array of data codeword bytes (including
|
||||
* segment headers and final padding, excluding error correction codewords),
|
||||
* supply the appropriate version number, and call the QrCode() constructor.
|
||||
* (Note that all ways require supplying the desired error correction level.)
|
||||
*/
|
||||
class QrCode final {
|
||||
|
||||
/*---- Public helper enumeration ----*/
|
||||
|
||||
/*
|
||||
* The error correction level in a QR Code symbol.
|
||||
*/
|
||||
public: enum class Ecc {
|
||||
LOW = 0 , // The QR Code can tolerate about 7% erroneous codewords
|
||||
MEDIUM , // The QR Code can tolerate about 15% erroneous codewords
|
||||
QUARTILE, // The QR Code can tolerate about 25% erroneous codewords
|
||||
HIGH , // The QR Code can tolerate about 30% erroneous codewords
|
||||
};
|
||||
|
||||
|
||||
// Returns a value in the range 0 to 3 (unsigned 2-bit integer).
|
||||
private: static int getFormatBits(Ecc ecl);
|
||||
|
||||
|
||||
|
||||
/*---- Static factory functions (high level) ----*/
|
||||
|
||||
/*
|
||||
* Returns a QR Code representing the given Unicode text string at the given error correction level.
|
||||
* As a conservative upper bound, this function is guaranteed to succeed for strings that have 2953 or fewer
|
||||
* UTF-8 code units (not Unicode code points) if the low error correction level is used. The smallest possible
|
||||
* QR Code version is automatically chosen for the output. The ECC level of the result may be higher than
|
||||
* the ecl argument if it can be done without increasing the version.
|
||||
*/
|
||||
public: static QrCode encodeText(const char *text, Ecc ecl);
|
||||
|
||||
|
||||
/*
|
||||
* Returns a QR Code representing the given binary data at the given error correction level.
|
||||
* This function always encodes using the binary segment mode, not any text mode. The maximum number of
|
||||
* bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output.
|
||||
* The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version.
|
||||
*/
|
||||
public: static QrCode encodeBinary(const std::vector<std::uint8_t> &data, Ecc ecl);
|
||||
|
||||
|
||||
/*---- Static factory functions (mid level) ----*/
|
||||
|
||||
/*
|
||||
* Returns a QR Code representing the given segments with the given encoding parameters.
|
||||
* The smallest possible QR Code version within the given range is automatically
|
||||
* chosen for the output. Iff boostEcl is true, then the ECC level of the result
|
||||
* may be higher than the ecl argument if it can be done without increasing the
|
||||
* version. The mask number is either between 0 to 7 (inclusive) to force that
|
||||
* mask, or -1 to automatically choose an appropriate mask (which may be slow).
|
||||
* This function allows the user to create a custom sequence of segments that switches
|
||||
* between modes (such as alphanumeric and byte) to encode text in less space.
|
||||
* This is a mid-level API; the high-level API is encodeText() and encodeBinary().
|
||||
*/
|
||||
public: static QrCode encodeSegments(const std::vector<QrSegment> &segs, Ecc ecl,
|
||||
int minVersion=1, int maxVersion=40, int mask=-1, bool boostEcl=true); // All optional parameters
|
||||
|
||||
|
||||
|
||||
/*---- Instance fields ----*/
|
||||
|
||||
// Immutable scalar parameters:
|
||||
|
||||
/* The version number of this QR Code, which is between 1 and 40 (inclusive).
|
||||
* This determines the size of this barcode. */
|
||||
private: int version;
|
||||
|
||||
/* The width and height of this QR Code, measured in modules, between
|
||||
* 21 and 177 (inclusive). This is equal to version * 4 + 17. */
|
||||
private: int size;
|
||||
|
||||
/* The error correction level used in this QR Code. */
|
||||
private: Ecc errorCorrectionLevel;
|
||||
|
||||
/* The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive).
|
||||
* Even if a QR Code is created with automatic masking requested (mask = -1),
|
||||
* the resulting object still has a mask value between 0 and 7. */
|
||||
private: int mask;
|
||||
|
||||
// Private grids of modules/pixels, with dimensions of size*size:
|
||||
|
||||
// The modules of this QR Code (false = light, true = dark).
|
||||
// Immutable after constructor finishes. Accessed through getModule().
|
||||
private: std::vector<std::vector<bool> > modules;
|
||||
|
||||
// Indicates function modules that are not subjected to masking. Discarded when constructor finishes.
|
||||
private: std::vector<std::vector<bool> > isFunction;
|
||||
|
||||
|
||||
|
||||
/*---- Constructor (low level) ----*/
|
||||
|
||||
/*
|
||||
* Creates a new QR Code with the given version number,
|
||||
* error correction level, data codeword bytes, and mask number.
|
||||
* This is a low-level API that most users should not use directly.
|
||||
* A mid-level API is the encodeSegments() function.
|
||||
*/
|
||||
public: QrCode(int ver, Ecc ecl, const std::vector<std::uint8_t> &dataCodewords, int msk);
|
||||
|
||||
|
||||
|
||||
/*---- Public instance methods ----*/
|
||||
|
||||
/*
|
||||
* Returns this QR Code's version, in the range [1, 40].
|
||||
*/
|
||||
public: int getVersion() const;
|
||||
|
||||
|
||||
/*
|
||||
* Returns this QR Code's size, in the range [21, 177].
|
||||
*/
|
||||
public: int getSize() const;
|
||||
|
||||
|
||||
/*
|
||||
* Returns this QR Code's error correction level.
|
||||
*/
|
||||
public: Ecc getErrorCorrectionLevel() const;
|
||||
|
||||
|
||||
/*
|
||||
* Returns this QR Code's mask, in the range [0, 7].
|
||||
*/
|
||||
public: int getMask() const;
|
||||
|
||||
|
||||
/*
|
||||
* Returns the color of the module (pixel) at the given coordinates, which is false
|
||||
* for light or true for dark. The top left corner has the coordinates (x=0, y=0).
|
||||
* If the given coordinates are out of bounds, then false (light) is returned.
|
||||
*/
|
||||
public: bool getModule(int x, int y) const;
|
||||
|
||||
|
||||
|
||||
/*---- Private helper methods for constructor: Drawing function modules ----*/
|
||||
|
||||
// Reads this object's version field, and draws and marks all function modules.
|
||||
private: void drawFunctionPatterns();
|
||||
|
||||
|
||||
// Draws two copies of the format bits (with its own error correction code)
|
||||
// based on the given mask and this object's error correction level field.
|
||||
private: void drawFormatBits(int msk);
|
||||
|
||||
|
||||
// Draws two copies of the version bits (with its own error correction code),
|
||||
// based on this object's version field, iff 7 <= version <= 40.
|
||||
private: void drawVersion();
|
||||
|
||||
|
||||
// Draws a 9*9 finder pattern including the border separator,
|
||||
// with the center module at (x, y). Modules can be out of bounds.
|
||||
private: void drawFinderPattern(int x, int y);
|
||||
|
||||
|
||||
// Draws a 5*5 alignment pattern, with the center module
|
||||
// at (x, y). All modules must be in bounds.
|
||||
private: void drawAlignmentPattern(int x, int y);
|
||||
|
||||
|
||||
// Sets the color of a module and marks it as a function module.
|
||||
// Only used by the constructor. Coordinates must be in bounds.
|
||||
private: void setFunctionModule(int x, int y, bool isDark);
|
||||
|
||||
|
||||
// Returns the color of the module at the given coordinates, which must be in range.
|
||||
private: bool module(int x, int y) const;
|
||||
|
||||
|
||||
/*---- Private helper methods for constructor: Codewords and masking ----*/
|
||||
|
||||
// Returns a new byte string representing the given data with the appropriate error correction
|
||||
// codewords appended to it, based on this object's version and error correction level.
|
||||
private: std::vector<std::uint8_t> addEccAndInterleave(const std::vector<std::uint8_t> &data) const;
|
||||
|
||||
|
||||
// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire
|
||||
// data area of this QR Code. Function modules need to be marked off before this is called.
|
||||
private: void drawCodewords(const std::vector<std::uint8_t> &data);
|
||||
|
||||
|
||||
// XORs the codeword modules in this QR Code with the given mask pattern.
|
||||
// The function modules must be marked and the codeword bits must be drawn
|
||||
// before masking. Due to the arithmetic of XOR, calling applyMask() with
|
||||
// the same mask value a second time will undo the mask. A final well-formed
|
||||
// QR Code needs exactly one (not zero, two, etc.) mask applied.
|
||||
private: void applyMask(int msk);
|
||||
|
||||
|
||||
// Calculates and returns the penalty score based on state of this QR Code's current modules.
|
||||
// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score.
|
||||
private: long getPenaltyScore() const;
|
||||
|
||||
|
||||
|
||||
/*---- Private helper functions ----*/
|
||||
|
||||
// Returns an ascending list of positions of alignment patterns for this version number.
|
||||
// Each position is in the range [0,177), and are used on both the x and y axes.
|
||||
// This could be implemented as lookup table of 40 variable-length lists of unsigned bytes.
|
||||
private: std::vector<int> getAlignmentPatternPositions() const;
|
||||
|
||||
|
||||
// Returns the number of data bits that can be stored in a QR Code of the given version number, after
|
||||
// all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8.
|
||||
// The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table.
|
||||
private: static int getNumRawDataModules(int ver);
|
||||
|
||||
|
||||
// Returns the number of 8-bit data (i.e. not error correction) codewords contained in any
|
||||
// QR Code of the given version number and error correction level, with remainder bits discarded.
|
||||
// This stateless pure function could be implemented as a (40*4)-cell lookup table.
|
||||
private: static int getNumDataCodewords(int ver, Ecc ecl);
|
||||
|
||||
|
||||
// Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be
|
||||
// implemented as a lookup table over all possible parameter values, instead of as an algorithm.
|
||||
private: static std::vector<std::uint8_t> reedSolomonComputeDivisor(int degree);
|
||||
|
||||
|
||||
// Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials.
|
||||
private: static std::vector<std::uint8_t> reedSolomonComputeRemainder(const std::vector<std::uint8_t> &data, const std::vector<std::uint8_t> &divisor);
|
||||
|
||||
|
||||
// Returns the product of the two given field elements modulo GF(2^8/0x11D).
|
||||
// All inputs are valid. This could be implemented as a 256*256 lookup table.
|
||||
private: static std::uint8_t reedSolomonMultiply(std::uint8_t x, std::uint8_t y);
|
||||
|
||||
|
||||
// Can only be called immediately after a light run is added, and
|
||||
// returns either 0, 1, or 2. A helper function for getPenaltyScore().
|
||||
private: int finderPenaltyCountPatterns(const std::array<int,7> &runHistory) const;
|
||||
|
||||
|
||||
// Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore().
|
||||
private: int finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, std::array<int,7> &runHistory) const;
|
||||
|
||||
|
||||
// Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore().
|
||||
private: void finderPenaltyAddHistory(int currentRunLength, std::array<int,7> &runHistory) const;
|
||||
|
||||
|
||||
// Returns true iff the i'th bit of x is set to 1.
|
||||
private: static bool getBit(long x, int i);
|
||||
|
||||
|
||||
/*---- Constants and tables ----*/
|
||||
|
||||
// The minimum version number supported in the QR Code Model 2 standard.
|
||||
public: static constexpr int MIN_VERSION = 1;
|
||||
|
||||
// The maximum version number supported in the QR Code Model 2 standard.
|
||||
public: static constexpr int MAX_VERSION = 40;
|
||||
|
||||
|
||||
// For use in getPenaltyScore(), when evaluating which mask is best.
|
||||
private: static const int PENALTY_N1;
|
||||
private: static const int PENALTY_N2;
|
||||
private: static const int PENALTY_N3;
|
||||
private: static const int PENALTY_N4;
|
||||
|
||||
|
||||
private: static const std::int8_t ECC_CODEWORDS_PER_BLOCK[4][41];
|
||||
private: static const std::int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41];
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*---- Public exception class ----*/
|
||||
|
||||
/*
|
||||
* Thrown when the supplied data does not fit any QR Code version. Ways to handle this exception include:
|
||||
* - Decrease the error correction level if it was greater than Ecc::LOW.
|
||||
* - If the encodeSegments() function was called with a maxVersion argument, then increase
|
||||
* it if it was less than QrCode::MAX_VERSION. (This advice does not apply to the other
|
||||
* factory functions because they search all versions up to QrCode::MAX_VERSION.)
|
||||
* - Split the text data into better or optimal segments in order to reduce the number of bits required.
|
||||
* - Change the text or binary data to be shorter.
|
||||
* - Change the text to fit the character set of a particular segment mode (e.g. alphanumeric).
|
||||
* - Propagate the error upward to the caller/user.
|
||||
*/
|
||||
class data_too_long : public std::length_error {
|
||||
|
||||
public: explicit data_too_long(const std::string &msg);
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* An appendable sequence of bits (0s and 1s). Mainly used by QrSegment.
|
||||
*/
|
||||
class BitBuffer final : public std::vector<bool> {
|
||||
|
||||
/*---- Constructor ----*/
|
||||
|
||||
// Creates an empty bit buffer (length 0).
|
||||
public: BitBuffer();
|
||||
|
||||
|
||||
|
||||
/*---- Method ----*/
|
||||
|
||||
// Appends the given number of low-order bits of the given value
|
||||
// to this buffer. Requires 0 <= len <= 31 and val < 2^len.
|
||||
public: void appendBits(std::uint32_t val, int len);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
245
CMakeLists.txt
Normal file
245
CMakeLists.txt
Normal file
@@ -0,0 +1,245 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
project(nekoray VERSION 0.1 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# WINDOWS PDB FILE
|
||||
if (WIN32)
|
||||
if (MSVC)
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi")
|
||||
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG /OPT:REF /OPT:ICF")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# Find Qt
|
||||
|
||||
if (NOT QT_VERSION_MAJOR)
|
||||
set(QT_VERSION_MAJOR 5)
|
||||
endif ()
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network Svg LinguistTools)
|
||||
|
||||
if (NKR_CROSS)
|
||||
set_property(TARGET Qt5::moc PROPERTY IMPORTED_LOCATION /usr/bin/moc)
|
||||
set_property(TARGET Qt5::uic PROPERTY IMPORTED_LOCATION /usr/bin/uic)
|
||||
set_property(TARGET Qt5::rcc PROPERTY IMPORTED_LOCATION /usr/bin/rcc)
|
||||
set_property(TARGET Qt5::lrelease PROPERTY IMPORTED_LOCATION /usr/bin/lrelease)
|
||||
set_property(TARGET Qt5::lupdate PROPERTY IMPORTED_LOCATION /usr/bin/lupdate)
|
||||
endif ()
|
||||
|
||||
# Windows
|
||||
include("cmake/fuck_windows/fuck.cmake")
|
||||
|
||||
# My dependencies
|
||||
include("cmake/print.cmake")
|
||||
set(MY_DEPS_DIR "${CMAKE_SOURCE_DIR}/libs/deps/built")
|
||||
list(APPEND CMAKE_PREFIX_PATH ${MY_DEPS_DIR})
|
||||
|
||||
# NKR
|
||||
include("cmake/nkr.cmake")
|
||||
|
||||
find_package(Threads)
|
||||
|
||||
if (NKR_NO_EXTERNAL)
|
||||
add_compile_definitions(NKR_NO_EXTERNAL)
|
||||
else ()
|
||||
if (NKR_NO_GRPC)
|
||||
add_compile_definitions(NKR_NO_GRPC)
|
||||
else ()
|
||||
# My proto
|
||||
include("cmake/myproto.cmake")
|
||||
list(APPEND NKR_EXTERNAL_TARGETS myproto)
|
||||
endif ()
|
||||
|
||||
# yaml-cpp (static)
|
||||
find_package(yaml-cpp CONFIG REQUIRED) # only Release is built
|
||||
list(APPEND NKR_EXTERNAL_TARGETS yaml-cpp)
|
||||
|
||||
# zxing-cpp
|
||||
find_package(ZXing CONFIG REQUIRED)
|
||||
list(APPEND NKR_EXTERNAL_TARGETS ZXing::ZXing)
|
||||
|
||||
# QHotkey (static submodule)
|
||||
set(QHOTKEY_INSTALL OFF)
|
||||
add_subdirectory(3rdparty/QHotkey)
|
||||
list(APPEND NKR_EXTERNAL_TARGETS qhotkey)
|
||||
endif ()
|
||||
|
||||
# debug print
|
||||
if (DBG_CMAKE)
|
||||
print_all_variables()
|
||||
print_target_properties(myproto)
|
||||
print_target_properties(yaml-cpp)
|
||||
print_target_properties(ZXing::ZXing)
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E time")
|
||||
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CMAKE_COMMAND} -E time")
|
||||
endif ()
|
||||
|
||||
# Sources
|
||||
set(PROJECT_SOURCES
|
||||
${PLATFORM_FUCKING_SOURCES}
|
||||
|
||||
main/main.cpp
|
||||
main/NekoRay.cpp
|
||||
main/NekoRay_Utils.cpp
|
||||
|
||||
3rdparty/qrcodegen.cpp
|
||||
3rdparty/QtExtKeySequenceEdit.cpp
|
||||
|
||||
qv2ray/ui/LogHighlighter.cpp
|
||||
qv2ray/ui/QvAutoCompleteTextEdit.cpp
|
||||
qv2ray/utils/HTTPRequestHelper.cpp
|
||||
qv2ray/components/proxy/QvProxyConfigurator.cpp
|
||||
qv2ray/ui/widgets/common/QJsonModel.cpp
|
||||
|
||||
qv2ray/ui/widgets/editors/w_JsonEditor.cpp
|
||||
qv2ray/ui/widgets/editors/w_JsonEditor.hpp
|
||||
qv2ray/ui/widgets/editors/w_JsonEditor.ui
|
||||
|
||||
rpc/gRPC.cpp
|
||||
|
||||
db/Database.cpp
|
||||
db/TrafficLooper.cpp
|
||||
db/ProfileFilter.cpp
|
||||
|
||||
fmt/AbstractBean.cpp
|
||||
fmt/Bean2CoreObj.cpp
|
||||
fmt/Bean2External.cpp
|
||||
fmt/Bean2Link.cpp
|
||||
fmt/InsecureHint.cpp
|
||||
fmt/Link2Bean.cpp
|
||||
db/ConfigBuilder.cpp
|
||||
fmt/ChainBean.hpp # translate
|
||||
|
||||
sub/GroupUpdater.cpp
|
||||
|
||||
sys/ExternalProcess.cpp
|
||||
sys/AutoRun.cpp
|
||||
|
||||
ui/ThemeManager.cpp
|
||||
|
||||
ui/mainwindow_grpc.cpp
|
||||
ui/mainwindow.cpp
|
||||
ui/mainwindow.h
|
||||
ui/mainwindow.ui
|
||||
|
||||
ui/edit/dialog_edit_profile.h
|
||||
ui/edit/dialog_edit_profile.cpp
|
||||
ui/edit/dialog_edit_profile.ui
|
||||
ui/edit/dialog_edit_group.h
|
||||
ui/edit/dialog_edit_group.cpp
|
||||
ui/edit/dialog_edit_group.ui
|
||||
|
||||
ui/edit/edit_chain.h
|
||||
ui/edit/edit_chain.cpp
|
||||
ui/edit/edit_chain.ui
|
||||
ui/edit/edit_socks_http.h
|
||||
ui/edit/edit_socks_http.cpp
|
||||
ui/edit/edit_socks_http.ui
|
||||
ui/edit/edit_shadowsocks.h
|
||||
ui/edit/edit_shadowsocks.cpp
|
||||
ui/edit/edit_shadowsocks.ui
|
||||
ui/edit/edit_vmess.h
|
||||
ui/edit/edit_vmess.cpp
|
||||
ui/edit/edit_vmess.ui
|
||||
ui/edit/edit_trojan_vless.h
|
||||
ui/edit/edit_trojan_vless.cpp
|
||||
ui/edit/edit_trojan_vless.ui
|
||||
|
||||
ui/edit/edit_naive.h
|
||||
ui/edit/edit_naive.cpp
|
||||
ui/edit/edit_naive.ui
|
||||
|
||||
ui/edit/edit_custom.h
|
||||
ui/edit/edit_custom.cpp
|
||||
ui/edit/edit_custom.ui
|
||||
|
||||
ui/dialog_basic_settings.cpp
|
||||
ui/dialog_basic_settings.h
|
||||
ui/dialog_basic_settings.ui
|
||||
|
||||
ui/dialog_manage_groups.cpp
|
||||
ui/dialog_manage_groups.h
|
||||
ui/dialog_manage_groups.ui
|
||||
|
||||
ui/dialog_manage_routes.cpp
|
||||
ui/dialog_manage_routes.h
|
||||
ui/dialog_manage_routes.ui
|
||||
|
||||
ui/dialog_hotkey.cpp
|
||||
ui/dialog_hotkey.h
|
||||
ui/dialog_hotkey.ui
|
||||
|
||||
ui/widget/ProxyItem.cpp
|
||||
ui/widget/ProxyItem.h
|
||||
ui/widget/ProxyItem.ui
|
||||
ui/widget/GroupItem.cpp
|
||||
ui/widget/GroupItem.h
|
||||
ui/widget/GroupItem.ui
|
||||
|
||||
res/neko.qrc
|
||||
res/theme/feiyangqingyun/qss.qrc
|
||||
${QV2RAY_RC}
|
||||
)
|
||||
|
||||
# Translations
|
||||
set(TS_FILES
|
||||
translations/zh_CN.ts
|
||||
)
|
||||
qt_create_translation(QM_FILES ${PROJECT_SOURCES} ${TS_FILES} OPTIONS -locations none)
|
||||
configure_file(translations/translations.qrc ${CMAKE_BINARY_DIR} COPYONLY)
|
||||
set(PROJECT_SOURCES ${PROJECT_SOURCES} ${TS_FILES} ${QM_FILES} ${CMAKE_BINARY_DIR}/translations.qrc)
|
||||
|
||||
# Qt exe
|
||||
if (${QT_VERSION_MAJOR} GREATER_EQUAL 6)
|
||||
qt_add_executable(nekoray
|
||||
MANUAL_FINALIZATION
|
||||
${PROJECT_SOURCES}
|
||||
)
|
||||
# Define target properties for Android with Qt 6 as:
|
||||
# set_property(TARGET nekoray APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
|
||||
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
|
||||
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
|
||||
else ()
|
||||
if (ANDROID)
|
||||
add_library(nekoray SHARED
|
||||
${PROJECT_SOURCES}
|
||||
)
|
||||
# Define properties for Android with Qt 5 after find_package() calls as:
|
||||
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
|
||||
else ()
|
||||
add_executable(nekoray
|
||||
${PROJECT_SOURCES}
|
||||
)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# Target
|
||||
|
||||
set_property(TARGET nekoray PROPERTY AUTOUIC ON)
|
||||
set_property(TARGET nekoray PROPERTY AUTOMOC ON)
|
||||
set_property(TARGET nekoray PROPERTY AUTORCC ON)
|
||||
|
||||
# Target Link
|
||||
|
||||
target_link_libraries(nekoray PRIVATE
|
||||
Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Svg
|
||||
Threads::Threads
|
||||
${NKR_EXTERNAL_TARGETS}
|
||||
${PLATFORM_FUCKING_LIBRARIES}
|
||||
)
|
||||
|
||||
set_target_properties(nekoray PROPERTIES
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
|
||||
MACOSX_BUNDLE TRUE
|
||||
WIN32_EXECUTABLE TRUE
|
||||
)
|
||||
|
||||
if (QT_VERSION_MAJOR EQUAL 6)
|
||||
qt_finalize_executable(nekoray)
|
||||
endif ()
|
||||
126
README.md
Normal file
126
README.md
Normal file
@@ -0,0 +1,126 @@
|
||||
# NekoRay
|
||||
|
||||
基于 Qt/C++ 的跨平台代理配置管理器( 使用 Matsuri 定制版 v2ray-core )
|
||||
|
||||
目前支持 Windows / Linux amd64 开箱即用
|
||||
|
||||
Qt/C++ based cross-platform proxy configuration manager ( Use Matsuri custom version of v2ray-core )
|
||||
|
||||
Support Windows / Linux amd64 out of the box now.
|
||||
|
||||
## 下载 Download
|
||||
|
||||
便携格式,无安装器。转到 Releases 下载预编译的二进制文件,解压后即可使用。
|
||||
|
||||
### GitHub Releases 下载
|
||||
|
||||
[](https://github.com/Matsuridayo/nekoray/releases)
|
||||
|
||||
## 更改记录 & 发布频道 Changelog & Telegram channel
|
||||
|
||||
https://t.me/Matsuridayo
|
||||
|
||||
## 项目主页 & 文档 Homepage & Documents
|
||||
|
||||
https://matsuridayo.github.io
|
||||
|
||||
### 运行参数
|
||||
|
||||
- `-many` 无视同目录正在运行的实例,强行开启新的实例 (0.11+)
|
||||
- `-appdata` 开启后配置文件会放在共享目录,无法多开和自动升级 (0.11+)
|
||||
|
||||
### 代理
|
||||
|
||||
| 协议 | 状态 | 配置编辑 | 分享链接生成 | 分享链接解析 | Clash 配置解析 |
|
||||
|--------------|--------|------|--------|-----------|------------|
|
||||
| Socks | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| HTTP | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Shadowsocks | ✅ (经典) | ✅ | ✅ | 常见格式 | ✅ |
|
||||
| VMess | ✅ | ✅ | ✅ | v2rayN 格式 | ✅ |
|
||||
| Trojan | ✅ | ✅ | ✅ | 标准&常见格式 | ✅ |
|
||||
| VLESS | ✅ | ✅ | ✅ | ✅ | 不适用 |
|
||||
| NaïveProxy | ✅ | ✅ | ✅ | ✅ | 不适用 |
|
||||
| Hysteria | ✅ | ❌ | ❌ | ❌ | 不适用 |
|
||||
|
||||
## Linux 运行 & 简易编译教程
|
||||
|
||||
**使用 Linux 系统相信您已具备基本的排错能力,
|
||||
本项目不提供特定发行版/架构的支持,预编译文件不能满足您的需求时,请自行编译/适配。**
|
||||
|
||||
系统要求: Qt5 运行环境,一般桌面 Linux 已经安装,如果没有请用包管理器安装,如
|
||||
|
||||
`apt install libqt5gui5 libqt5x11extras5`
|
||||
|
||||
运行: `./launcher` 或 部分系统可双击打开
|
||||
|
||||
launcher 参数
|
||||
|
||||
* `./launcher -- -appdata` `--` 后的参数传递给主程序
|
||||
* `-debug` Debug mode
|
||||
* `-theme` Use local QT theme (unstable) (1.0+)
|
||||
|
||||
已知部分 x86_64 Linux 发行版无法使用预编译版、非 x86_64 暂无适配,可以尝试自行编译。
|
||||
|
||||
### 编译
|
||||
|
||||
准备工作
|
||||
|
||||
```
|
||||
git submodule init
|
||||
git submodule update
|
||||
```
|
||||
|
||||
| CMake 参数 | 默认值 | 含义 |
|
||||
|-------------------------|-----|-------------------------|
|
||||
| QT_VERSION_MAJOR | 5 | QT版本 |
|
||||
| NKR_NO_EXTERNAL | | 不包含外部C++依赖(如ZXing/gRPC) |
|
||||
| NKR_NO_GRPC | | 不包含gRPC |
|
||||
| NKR_CROSS | | |
|
||||
|
||||
### 简单编译法
|
||||
|
||||
条件:
|
||||
|
||||
1. C++ 依赖: `qt5 protobuf yaml-cpp zxing-cpp` 已用包管理器安装,并符合版本要求
|
||||
2. Qt 版本必须大于等于 5.15
|
||||
3. 系统为 `x86-64-linux-gnu`
|
||||
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -GNinja ..
|
||||
ninja
|
||||
```
|
||||
|
||||
编译完成后得到 `nekoray`
|
||||
|
||||
解压 Release 的压缩包,替换其中的 `nekoray`,删除 `launcher` 即可使用。
|
||||
|
||||
### 复杂编译法
|
||||
|
||||
C++ 部分
|
||||
|
||||
当您的发行版没有上面几个 C++ 依赖包,或者版本不符合要求时,可以参考 libs 文件夹内的默认编译脚本自行编译。
|
||||
|
||||
依赖搜寻 prefix 为 `libs/deps/bulit`
|
||||
|
||||
编译完成后得到 `nekoray`
|
||||
|
||||
Go 部分
|
||||
|
||||
1. 把 `Matsuridayo/Matsuri` `Matsuridayo/v2ray-core` 置于 `../`
|
||||
2. 进入 `go` 文件夹 `go build` 得到 `nekoray_core`。
|
||||
|
||||
非官方构建无需编译 `updater` `launcher`
|
||||
|
||||
## Credits
|
||||
|
||||
- [v2fly/v2ray-core](https://github.com/v2fly/v2ray-core)
|
||||
- [MatsuriDayo/Matsuri](https://github.com/MatsuriDayo/Matsuri)
|
||||
- [MatsuriDayo/v2ray-core](https://github.com/MatsuriDayo/v2ray-core)
|
||||
- [SagerNet/sing-box](https://github.com/SagerNet/sing-box)
|
||||
- [Qt](https://www.qt.io/)
|
||||
- [protobuf](https://github.com/protocolbuffers/protobuf)
|
||||
- [yaml-cpp](https://github.com/jbeder/yaml-cpp)
|
||||
- [zxing-cpp](https://github.com/nu-book/zxing-cpp)
|
||||
- [QHotkey](https://github.com/Skycoder42/QHotkey)
|
||||
BIN
assets/nekoray.png
Normal file
BIN
assets/nekoray.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
BIN
assets/qtbase_zh_CN.qm
Normal file
BIN
assets/qtbase_zh_CN.qm
Normal file
Binary file not shown.
82
cmake/fuck_windows/VersionInfo.in
Normal file
82
cmake/fuck_windows/VersionInfo.in
Normal file
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef PRODUCT_VERSION_MAJOR
|
||||
#define PRODUCT_VERSION_MAJOR @PRODUCT_VERSION_MAJOR@
|
||||
#endif
|
||||
|
||||
#ifndef PRODUCT_VERSION_MINOR
|
||||
#define PRODUCT_VERSION_MINOR @PRODUCT_VERSION_MINOR@
|
||||
#endif
|
||||
|
||||
#ifndef PRODUCT_VERSION_PATCH
|
||||
#define PRODUCT_VERSION_PATCH @PRODUCT_VERSION_PATCH@
|
||||
#endif
|
||||
|
||||
#ifndef PRODUCT_VERSION_BUILD
|
||||
#define PRODUCT_VERSION_BUILD @PRODUCT_VERSION_REVISION@
|
||||
#endif
|
||||
|
||||
#ifndef FILE_VERSION_MAJOR
|
||||
#define FILE_VERSION_MAJOR @PRODUCT_VERSION_MAJOR@
|
||||
#endif
|
||||
|
||||
#ifndef FILE_VERSION_MINOR
|
||||
#define FILE_VERSION_MINOR @PRODUCT_VERSION_MINOR@
|
||||
#endif
|
||||
|
||||
#ifndef FILE_VERSION_PATCH
|
||||
#define FILE_VERSION_PATCH @PRODUCT_VERSION_PATCH@
|
||||
#endif
|
||||
|
||||
#ifndef FILE_VERSION_BUILD
|
||||
#define FILE_VERSION_BUILD @PRODUCT_VERSION_REVISION@
|
||||
#endif
|
||||
|
||||
#ifndef __TO_STRING
|
||||
#define __TO_STRING_IMPL(x) #x
|
||||
#define __TO_STRING(x) __TO_STRING_IMPL(x)
|
||||
#endif
|
||||
|
||||
#define PRODUCT_VERSION_MAJOR_MINOR_STR __TO_STRING(PRODUCT_VERSION_MAJOR) "." __TO_STRING(PRODUCT_VERSION_MINOR)
|
||||
#define PRODUCT_VERSION_MAJOR_MINOR_PATCH_STR PRODUCT_VERSION_MAJOR_MINOR_STR "." __TO_STRING(PRODUCT_VERSION_PATCH)
|
||||
#define PRODUCT_VERSION_FULL_STR PRODUCT_VERSION_MAJOR_MINOR_PATCH_STR "." __TO_STRING(PRODUCT_VERSION_BUILD)
|
||||
#define PRODUCT_VERSION_RESOURCE PRODUCT_VERSION_MAJOR,PRODUCT_VERSION_MINOR,PRODUCT_VERSION_PATCH,PRODUCT_VERSION_BUILD
|
||||
#define PRODUCT_VERSION_RESOURCE_STR PRODUCT_VERSION_FULL_STR "\0"
|
||||
|
||||
#define FILE_VERSION_MAJOR_MINOR_STR __TO_STRING(FILE_VERSION_MAJOR) "." __TO_STRING(FILE_VERSION_MINOR)
|
||||
#define FILE_VERSION_MAJOR_MINOR_PATCH_STR FILE_VERSION_MAJOR_MINOR_STR "." __TO_STRING(FILE_VERSION_PATCH)
|
||||
#define FILE_VERSION_FULL_STR FILE_VERSION_MAJOR_MINOR_PATCH_STR "." __TO_STRING(FILE_VERSION_BUILD)
|
||||
#define FILE_VERSION_RESOURCE FILE_VERSION_MAJOR,FILE_VERSION_MINOR,FILE_VERSION_PATCH,FILE_VERSION_BUILD
|
||||
#define FILE_VERSION_RESOURCE_STR FILE_VERSION_FULL_STR "\0"
|
||||
|
||||
#ifndef PRODUCT_ICON
|
||||
#define PRODUCT_ICON "@PRODUCT_ICON@"
|
||||
#endif
|
||||
|
||||
#ifndef PRODUCT_COMMENTS
|
||||
#define PRODUCT_COMMENTS "@PRODUCT_COMMENTS@\0"
|
||||
#endif
|
||||
|
||||
#ifndef PRODUCT_COMPANY_NAME
|
||||
#define PRODUCT_COMPANY_NAME "@PRODUCT_COMPANY_NAME@\0"
|
||||
#endif
|
||||
|
||||
#ifndef PRODUCT_COMPANY_COPYRIGHT
|
||||
#define PRODUCT_COMPANY_COPYRIGHT "@PRODUCT_COMPANY_COPYRIGHT@\0"
|
||||
#endif
|
||||
|
||||
#ifndef PRODUCT_FILE_DESCRIPTION
|
||||
#define PRODUCT_FILE_DESCRIPTION "@PRODUCT_FILE_DESCRIPTION@\0"
|
||||
#endif
|
||||
|
||||
#ifndef PRODUCT_INTERNAL_NAME
|
||||
#define PRODUCT_INTERNAL_NAME "@PRODUCT_NAME@\0"
|
||||
#endif
|
||||
|
||||
#ifndef PRODUCT_ORIGINAL_FILENAME
|
||||
#define PRODUCT_ORIGINAL_FILENAME "@PRODUCT_ORIGINAL_FILENAME@\0"
|
||||
#endif
|
||||
|
||||
#ifndef PRODUCT_BUNDLE
|
||||
#define PRODUCT_BUNDLE "@PRODUCT_BUNDLE@\0"
|
||||
#endif
|
||||
52
cmake/fuck_windows/VersionResource.rc
Normal file
52
cmake/fuck_windows/VersionResource.rc
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "VersionInfo.h"
|
||||
|
||||
#if defined(__MINGW64__) || defined(__MINGW32__)
|
||||
// MinGW-w64, MinGW
|
||||
#if defined(__has_include) && __has_include(<winres.h>)
|
||||
#include <winres.h>
|
||||
#else
|
||||
#include <afxres.h>
|
||||
#include <winresrc.h>
|
||||
#endif
|
||||
#else
|
||||
// MSVC, Windows SDK
|
||||
#include <winres.h>
|
||||
#endif
|
||||
|
||||
IDI_ICON1 ICON PRODUCT_ICON
|
||||
|
||||
LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION FILE_VERSION_RESOURCE
|
||||
PRODUCTVERSION PRODUCT_VERSION_RESOURCE
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS 0x4L
|
||||
FILETYPE 0x1L
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "000904b0"
|
||||
BEGIN
|
||||
VALUE "Comments", PRODUCT_COMMENTS
|
||||
VALUE "CompanyName", PRODUCT_COMPANY_NAME
|
||||
VALUE "FileDescription", PRODUCT_FILE_DESCRIPTION
|
||||
VALUE "FileVersion", FILE_VERSION_RESOURCE_STR
|
||||
VALUE "InternalName", PRODUCT_INTERNAL_NAME
|
||||
VALUE "LegalCopyright", PRODUCT_COMPANY_COPYRIGHT
|
||||
VALUE "OriginalFilename", PRODUCT_ORIGINAL_FILENAME
|
||||
VALUE "ProductName", PRODUCT_BUNDLE
|
||||
VALUE "ProductVersion", PRODUCT_VERSION_RESOURCE_STR
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x9, 1200
|
||||
END
|
||||
END
|
||||
27
cmake/fuck_windows/fuck.cmake
Normal file
27
cmake/fuck_windows/fuck.cmake
Normal file
@@ -0,0 +1,27 @@
|
||||
if (WIN32)
|
||||
set(PLATFORM_FUCKING_SOURCES 3rdparty/WinCommander.cpp)
|
||||
|
||||
include(cmake/fuck_windows/generate_product_version.cmake)
|
||||
generate_product_version(
|
||||
QV2RAY_RC
|
||||
NAME "Nekoray"
|
||||
BUNDLE "Nekoray Project Family"
|
||||
ICON "${CMAKE_SOURCE_DIR}/res/nekoray.ico"
|
||||
COMPANY_NAME "Nekoray Workgroup"
|
||||
COMPANY_COPYRIGHT "Nekoray Workgroup"
|
||||
FILE_DESCRIPTION "Nekoray Main Application"
|
||||
)
|
||||
add_definitions(-DUNICODE -D_UNICODE -DNOMINMAX)
|
||||
set(GUI_TYPE WIN32)
|
||||
if (MINGW)
|
||||
if (NOT DEFINED MinGW_ROOT)
|
||||
set(MinGW_ROOT "C:/msys64/mingw64")
|
||||
endif ()
|
||||
else ()
|
||||
add_compile_options("/utf-8")
|
||||
add_compile_options("/std:c++17")
|
||||
add_definitions(-D_WIN32_WINNT=0x600 -D_SCL_SECURE_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS)
|
||||
set(PLATFORM_FUCKING_LIBRARIES wininet wsock32 ws2_32 user32 Rasapi32 Iphlpapi)
|
||||
list(APPEND PLATFORM_FUCKING_SOURCES sys/windows/MiniDump.cpp)
|
||||
endif ()
|
||||
endif ()
|
||||
107
cmake/fuck_windows/generate_product_version.cmake
Normal file
107
cmake/fuck_windows/generate_product_version.cmake
Normal file
@@ -0,0 +1,107 @@
|
||||
include (CMakeParseArguments)
|
||||
|
||||
set (GenerateProductVersionCurrentDir ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
# generate_product_version() function
|
||||
#
|
||||
# This function uses VersionInfo.in template file and VersionResource.rc file
|
||||
# to generate WIN32 resource with version information and general resource strings.
|
||||
#
|
||||
# Usage:
|
||||
# generate_product_version(
|
||||
# SomeOutputResourceVariable
|
||||
# NAME MyGreatProject
|
||||
# ICON ${PATH_TO_APP_ICON}
|
||||
# VERSION_MAJOR 2
|
||||
# VERSION_MINOR 3
|
||||
# VERSION_PATCH ${BUILD_COUNTER}
|
||||
# VERSION_REVISION ${BUILD_REVISION}
|
||||
# )
|
||||
# where BUILD_COUNTER and BUILD_REVISION could be values from your CI server.
|
||||
#
|
||||
# You can use generated resource for your executable targets:
|
||||
# add_executable(target-name ${target-files} ${SomeOutputResourceVariable})
|
||||
#
|
||||
# You can specify resource strings in arguments:
|
||||
# NAME - name of executable (no defaults, ex: Microsoft Word)
|
||||
# BUNDLE - bundle (${NAME} is default, ex: Microsoft Office)
|
||||
# ICON - path to application icon (${CMAKE_SOURCE_DIR}/product.ico by default)
|
||||
# VERSION_MAJOR - 1 is default
|
||||
# VERSION_MINOR - 0 is default
|
||||
# VERSION_PATCH - 0 is default
|
||||
# VERSION_REVISION - 0 is default
|
||||
# COMPANY_NAME - your company name (no defaults)
|
||||
# COMPANY_COPYRIGHT - ${COMPANY_NAME} (C) Copyright ${CURRENT_YEAR} is default
|
||||
# COMMENTS - ${NAME} v${VERSION_MAJOR}.${VERSION_MINOR} is default
|
||||
# ORIGINAL_FILENAME - ${NAME} is default
|
||||
# INTERNAL_NAME - ${NAME} is default
|
||||
# FILE_DESCRIPTION - ${NAME} is default
|
||||
function(generate_product_version outfiles)
|
||||
set (options)
|
||||
set (oneValueArgs
|
||||
NAME
|
||||
BUNDLE
|
||||
ICON
|
||||
VERSION_MAJOR
|
||||
VERSION_MINOR
|
||||
VERSION_PATCH
|
||||
VERSION_REVISION
|
||||
COMPANY_NAME
|
||||
COMPANY_COPYRIGHT
|
||||
COMMENTS
|
||||
ORIGINAL_FILENAME
|
||||
INTERNAL_NAME
|
||||
FILE_DESCRIPTION)
|
||||
set (multiValueArgs)
|
||||
cmake_parse_arguments(PRODUCT "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
|
||||
if (NOT PRODUCT_BUNDLE OR "${PRODUCT_BUNDLE}" STREQUAL "")
|
||||
set(PRODUCT_BUNDLE "${PRODUCT_NAME}")
|
||||
endif()
|
||||
if (NOT PRODUCT_ICON OR "${PRODUCT_ICON}" STREQUAL "")
|
||||
set(PRODUCT_ICON "${CMAKE_SOURCE_DIR}/product.ico")
|
||||
endif()
|
||||
|
||||
if (NOT PRODUCT_VERSION_MAJOR EQUAL 0 AND (NOT PRODUCT_VERSION_MAJOR OR "${PRODUCT_VERSION_MAJOR}" STREQUAL ""))
|
||||
set(PRODUCT_VERSION_MAJOR 1)
|
||||
endif()
|
||||
if (NOT PRODUCT_VERSION_MINOR EQUAL 0 AND (NOT PRODUCT_VERSION_MINOR OR "${PRODUCT_VERSION_MINOR}" STREQUAL ""))
|
||||
set(PRODUCT_VERSION_MINOR 0)
|
||||
endif()
|
||||
if (NOT PRODUCT_VERSION_PATCH EQUAL 0 AND (NOT PRODUCT_VERSION_PATCH OR "${PRODUCT_VERSION_PATCH}" STREQUAL ""))
|
||||
set(PRODUCT_VERSION_PATCH 0)
|
||||
endif()
|
||||
if (NOT PRODUCT_VERSION_REVISION EQUAL 0 AND (NOT PRODUCT_VERSION_REVISION OR "${PRODUCT_VERSION_REVISION}" STREQUAL ""))
|
||||
set(PRODUCT_VERSION_REVISION 0)
|
||||
endif()
|
||||
|
||||
if (NOT PRODUCT_COMPANY_COPYRIGHT OR "${PRODUCT_COMPANY_COPYRIGHT}" STREQUAL "")
|
||||
string(TIMESTAMP PRODUCT_CURRENT_YEAR "%Y")
|
||||
set(PRODUCT_COMPANY_COPYRIGHT "${PRODUCT_COMPANY_NAME} (C) Copyright ${PRODUCT_CURRENT_YEAR}")
|
||||
endif()
|
||||
if (NOT PRODUCT_COMMENTS OR "${PRODUCT_COMMENTS}" STREQUAL "")
|
||||
set(PRODUCT_COMMENTS "${PRODUCT_NAME} v${PRODUCT_VERSION_MAJOR}.${PRODUCT_VERSION_MINOR}")
|
||||
endif()
|
||||
if (NOT PRODUCT_ORIGINAL_FILENAME OR "${PRODUCT_ORIGINAL_FILENAME}" STREQUAL "")
|
||||
set(PRODUCT_ORIGINAL_FILENAME "${PRODUCT_NAME}")
|
||||
endif()
|
||||
if (NOT PRODUCT_INTERNAL_NAME OR "${PRODUCT_INTERNAL_NAME}" STREQUAL "")
|
||||
set(PRODUCT_INTERNAL_NAME "${PRODUCT_NAME}")
|
||||
endif()
|
||||
if (NOT PRODUCT_FILE_DESCRIPTION OR "${PRODUCT_FILE_DESCRIPTION}" STREQUAL "")
|
||||
set(PRODUCT_FILE_DESCRIPTION "${PRODUCT_NAME}")
|
||||
endif()
|
||||
|
||||
set (_VersionInfoFile ${CMAKE_CURRENT_BINARY_DIR}/VersionInfo.h)
|
||||
set (_VersionResourceFile ${CMAKE_CURRENT_BINARY_DIR}/VersionResource.rc)
|
||||
configure_file(
|
||||
${GenerateProductVersionCurrentDir}/VersionInfo.in
|
||||
${_VersionInfoFile}
|
||||
@ONLY)
|
||||
configure_file(
|
||||
${GenerateProductVersionCurrentDir}/VersionResource.rc
|
||||
${_VersionResourceFile}
|
||||
COPYONLY)
|
||||
list(APPEND ${outfiles} ${_VersionInfoFile} ${_VersionResourceFile})
|
||||
set (${outfiles} ${${outfiles}} PARENT_SCOPE)
|
||||
endfunction()
|
||||
14
cmake/myproto.cmake
Normal file
14
cmake/myproto.cmake
Normal file
@@ -0,0 +1,14 @@
|
||||
find_package(Protobuf CONFIG REQUIRED)
|
||||
|
||||
set(PROTO_FILES
|
||||
go/gen/libcore.proto
|
||||
)
|
||||
|
||||
add_library(myproto ${PROTO_FILES})
|
||||
target_link_libraries(myproto
|
||||
PUBLIC
|
||||
protobuf::libprotobuf
|
||||
)
|
||||
target_include_directories(myproto PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
protobuf_generate(TARGET myproto LANGUAGE cpp)
|
||||
10
cmake/nkr.cmake
Normal file
10
cmake/nkr.cmake
Normal file
@@ -0,0 +1,10 @@
|
||||
# Release
|
||||
file(STRINGS nekoray_version.txt NKR_VERSION)
|
||||
add_compile_definitions(NKR_VERSION=\"${NKR_VERSION}\")
|
||||
|
||||
# Debug
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DNKR_DEBUG")
|
||||
|
||||
if (NKR_USE_APPDATA)
|
||||
add_compile_definitions(NKR_USE_APPDATA)
|
||||
endif ()
|
||||
43
cmake/print.cmake
Normal file
43
cmake/print.cmake
Normal file
@@ -0,0 +1,43 @@
|
||||
macro(print_all_variables)
|
||||
message(STATUS "print_all_variables------------------------------------------{")
|
||||
get_cmake_property(_variableNames VARIABLES)
|
||||
foreach (_variableName ${_variableNames})
|
||||
message(STATUS "${_variableName}=${${_variableName}}")
|
||||
endforeach()
|
||||
message(STATUS "print_all_variables------------------------------------------}")
|
||||
endmacro()
|
||||
|
||||
# Get all propreties that cmake supports
|
||||
if(NOT CMAKE_PROPERTY_LIST)
|
||||
execute_process(COMMAND cmake --help-property-list OUTPUT_VARIABLE CMAKE_PROPERTY_LIST)
|
||||
|
||||
# Convert command output into a CMake list
|
||||
string(REGEX REPLACE ";" "\\\\;" CMAKE_PROPERTY_LIST "${CMAKE_PROPERTY_LIST}")
|
||||
string(REGEX REPLACE "\n" ";" CMAKE_PROPERTY_LIST "${CMAKE_PROPERTY_LIST}")
|
||||
endif()
|
||||
|
||||
function(print_properties)
|
||||
message("CMAKE_PROPERTY_LIST = ${CMAKE_PROPERTY_LIST}")
|
||||
endfunction()
|
||||
|
||||
function(print_target_properties target)
|
||||
if(NOT TARGET ${target})
|
||||
message(STATUS "There is no target named '${target}'")
|
||||
return()
|
||||
endif()
|
||||
|
||||
foreach(property ${CMAKE_PROPERTY_LIST})
|
||||
string(REPLACE "<CONFIG>" "${CMAKE_BUILD_TYPE}" property ${property})
|
||||
|
||||
# Fix https://stackoverflow.com/questions/32197663/how-can-i-remove-the-the-location-property-may-not-be-read-from-target-error-i
|
||||
if(property STREQUAL "LOCATION" OR property MATCHES "^LOCATION_" OR property MATCHES "_LOCATION$")
|
||||
continue()
|
||||
endif()
|
||||
|
||||
get_property(was_set TARGET ${target} PROPERTY ${property} SET)
|
||||
if(was_set)
|
||||
get_target_property(value ${target} ${property})
|
||||
message("${target} ${property} = ${value}")
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
1
core_commit.txt
Normal file
1
core_commit.txt
Normal file
@@ -0,0 +1 @@
|
||||
edf4fcff77a0dfd40b20076faf45767a3394f5eb
|
||||
475
db/ConfigBuilder.cpp
Normal file
475
db/ConfigBuilder.cpp
Normal file
@@ -0,0 +1,475 @@
|
||||
#include "db/ConfigBuilder.hpp"
|
||||
#include "db/Database.hpp"
|
||||
#include "fmt/includes.h"
|
||||
|
||||
namespace NekoRay {
|
||||
|
||||
void ApplyCustomOutboundJsonSettings(const QJsonObject &custom, QJsonObject &outbound) {
|
||||
// 合并
|
||||
if (custom.isEmpty()) return;
|
||||
for (const auto &key: custom.keys()) {
|
||||
if (outbound.contains(key)) {
|
||||
auto v = custom[key];
|
||||
auto v_orig = outbound[key];
|
||||
if (v.isObject() && v_orig.isObject()) {// isObject 则合并?
|
||||
auto vo = v.toObject();
|
||||
QJsonObject vo_orig = v_orig.toObject();
|
||||
ApplyCustomOutboundJsonSettings(vo, vo_orig);
|
||||
outbound[key] = vo_orig;
|
||||
} else {
|
||||
outbound[key] = v;
|
||||
}
|
||||
} else {
|
||||
outbound[key] = custom[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QSharedPointer<BuildConfigResult> BuildConfig(const QSharedPointer<ProxyEntity> &ent, bool forTest) {
|
||||
auto result = QSharedPointer<BuildConfigResult>(new BuildConfigResult);
|
||||
auto status = QSharedPointer<BuildConfigStatus>(new BuildConfigStatus);
|
||||
status->result = result;
|
||||
|
||||
// Log
|
||||
auto logObj = QJsonObject{{"loglevel", dataStore->log_level}};
|
||||
result->coreConfig.insert("log", logObj);
|
||||
|
||||
// Inbounds
|
||||
QJsonObject sniffing{{"destOverride", dataStore->fake_dns ?
|
||||
QJsonArray{"fakedns", "http", "tls", "quic"}
|
||||
: QJsonArray{"http", "tls", "quic"}},
|
||||
{"enabled", true},
|
||||
{"metadataOnly", false},
|
||||
{"routeOnly", dataStore->sniffing_mode == SniffingMode::FOR_ROUTING},};
|
||||
|
||||
// socks-in
|
||||
if (InRange(dataStore->inbound_socks_port, 0, 65535) && !forTest) {
|
||||
QJsonObject socksInbound;
|
||||
socksInbound["tag"] = "socks-in";
|
||||
socksInbound["protocol"] = "socks";
|
||||
socksInbound["listen"] = dataStore->inbound_address;
|
||||
socksInbound["port"] = dataStore->inbound_socks_port;
|
||||
socksInbound["settings"] = QJsonObject({{"auth", "noauth"},
|
||||
{"udp", true},});
|
||||
if (dataStore->fake_dns || dataStore->sniffing_mode != SniffingMode::DISABLE) {
|
||||
socksInbound["sniffing"] = sniffing;
|
||||
}
|
||||
status->inbounds += socksInbound;
|
||||
}
|
||||
// http-in
|
||||
if (InRange(dataStore->inbound_http_port, 0, 65535) && !forTest) {
|
||||
QJsonObject socksInbound;
|
||||
socksInbound["tag"] = "http-in";
|
||||
socksInbound["protocol"] = "http";
|
||||
socksInbound["listen"] = dataStore->inbound_address;
|
||||
socksInbound["port"] = dataStore->inbound_http_port;
|
||||
if (dataStore->sniffing_mode != SniffingMode::DISABLE) {
|
||||
socksInbound["sniffing"] = sniffing;
|
||||
}
|
||||
status->inbounds += socksInbound;
|
||||
}
|
||||
|
||||
// Outbounds
|
||||
QList<QSharedPointer<ProxyEntity>> ents;
|
||||
if (ent->type == "chain") {
|
||||
auto list = ent->ChainBean()->list;
|
||||
std::reverse(std::begin(list), std::end(list));
|
||||
for (auto id: list) {
|
||||
ents += profileManager->GetProfile(id);
|
||||
if (ents.last() == nullptr) {
|
||||
result->error = QString("chain missing ent: %1").arg(id);
|
||||
return result;
|
||||
}
|
||||
if (ents.last()->type == "chain") {
|
||||
result->error = QString("chain in chain is not allowed: %1").arg(id);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ents += ent;
|
||||
}
|
||||
status->currentEnt = ent.get();
|
||||
QString tagProxy = BuildChain(0, ents, status);
|
||||
if (!result->error.isEmpty()) return result;
|
||||
|
||||
// direct & bypass & block
|
||||
status->outbounds += QJsonObject{{"protocol", "freedom"},
|
||||
{"tag", "direct"},};
|
||||
status->outbounds += QJsonObject{{"protocol", "freedom"},
|
||||
{"tag", "bypass"},};
|
||||
status->outbounds += QJsonObject{{"protocol", "blackhole"},
|
||||
{"tag", "block"},};
|
||||
|
||||
// block for tun
|
||||
if (!forTest) {
|
||||
status->routingRules += QJsonObject{{"type", "field"},
|
||||
{"ip", QJsonArray{"224.0.0.0/3", "169.254.0.0/16",},},
|
||||
{"outboundTag", "block"},};
|
||||
status->routingRules += QJsonObject{{"type", "field"},
|
||||
{"port", "135-139"},
|
||||
{"outboundTag", "block"},};
|
||||
}
|
||||
|
||||
// DNS Routing (tun2socks 用到,防污染)
|
||||
if (dataStore->dns_routing && !forTest) {
|
||||
QJsonObject dnsOut;
|
||||
dnsOut["protocol"] = "dns";
|
||||
dnsOut["tag"] = "dns-out";
|
||||
QJsonObject dnsOut_settings;
|
||||
dnsOut_settings["network"] = "tcp";
|
||||
dnsOut_settings["port"] = 53;
|
||||
dnsOut_settings["address"] = "8.8.8.8";
|
||||
dnsOut_settings["userLevel"] = 1;
|
||||
dnsOut["settings"] = dnsOut_settings;
|
||||
dnsOut["proxySettings"] = QJsonObject{
|
||||
{"tag", tagProxy},
|
||||
{"transportLayer", true}
|
||||
};
|
||||
|
||||
status->outbounds += dnsOut;
|
||||
status->routingRules += QJsonObject{
|
||||
{"type", "field"},
|
||||
{"port", "53"},
|
||||
{"inboundTag", QJsonArray{"socks-in", "http-in"}},
|
||||
{"outboundTag", "dns-out"},
|
||||
};
|
||||
status->routingRules += QJsonObject{
|
||||
{"type", "field"},
|
||||
{"inboundTag", QJsonArray{"dns-in"}},
|
||||
{"outboundTag", "dns-out"},
|
||||
};
|
||||
}
|
||||
|
||||
// custom inbound
|
||||
QJSONARRAY_ADD(status->inbounds, QString2QJsonObject(dataStore->custom_inbound)["inbounds"].toArray())
|
||||
|
||||
result->coreConfig.insert("inbounds", status->inbounds);
|
||||
result->coreConfig.insert("outbounds", status->outbounds);
|
||||
|
||||
// dns domain user rule
|
||||
for (const auto &line: SplitLines(dataStore->routing->proxy_domain)) {
|
||||
if (line.startsWith("#")) continue;
|
||||
if (dataStore->dns_routing) status->domainListDNSRemote += line;
|
||||
status->domainListRemote += line;
|
||||
}
|
||||
for (const auto &line: SplitLines(dataStore->routing->direct_domain)) {
|
||||
if (line.startsWith("#")) continue;
|
||||
if (dataStore->dns_routing) status->domainListDNSDirect += line;
|
||||
status->domainListDirect += line;
|
||||
}
|
||||
for (const auto &line: SplitLines(dataStore->routing->block_domain)) {
|
||||
if (line.startsWith("#")) continue;
|
||||
status->domainListBlock += line;
|
||||
}
|
||||
|
||||
// final add DNS
|
||||
QJsonObject dns;
|
||||
QJsonArray dnsServers;
|
||||
|
||||
// FakeDNS
|
||||
QJsonObject dnsServerFake;
|
||||
dnsServerFake["address"] = "fakedns";
|
||||
dnsServerFake["domains"] = status->domainListDNSRemote;
|
||||
if (dataStore->fake_dns && !forTest) dnsServers += dnsServerFake;
|
||||
|
||||
// remote
|
||||
QJsonObject dnsServerRemote;
|
||||
dnsServerRemote["address"] = dataStore->remote_dns;
|
||||
dnsServerRemote["domains"] = status->domainListDNSRemote;
|
||||
if (!forTest) dnsServers += dnsServerRemote;
|
||||
|
||||
//direct
|
||||
auto directDnsAddress = dataStore->direct_dns;
|
||||
if (directDnsAddress.contains("://")) {
|
||||
auto directDnsIp = SubStrBefore(SubStrAfter(directDnsAddress, "://"), "/");
|
||||
if (IsIpAddress(directDnsIp)) {
|
||||
status->routingRules.push_front(QJsonObject{
|
||||
{"type", "field"},
|
||||
{"ip", QJsonArray{directDnsIp}},
|
||||
{"outboundTag", "direct"},
|
||||
});
|
||||
} else {
|
||||
status->routingRules.push_front(QJsonObject{
|
||||
{"type", "field"},
|
||||
{"domain", QJsonArray{directDnsIp}},
|
||||
{"outboundTag", "direct"},
|
||||
});
|
||||
}
|
||||
} else if (directDnsAddress != "localhost") {
|
||||
status->routingRules.push_front(QJsonObject{
|
||||
{"type", "field"},
|
||||
{"ip", QJsonArray{directDnsAddress}},
|
||||
{"outboundTag", "direct"},
|
||||
});
|
||||
}
|
||||
dnsServers += QJsonObject{{"address", directDnsAddress},
|
||||
{"domains", status->domainListDNSDirect},
|
||||
{"skipFallback", true},};
|
||||
|
||||
dns["disableFallbackIfMatch"] = true;
|
||||
dns["hosts"] = status->hosts;
|
||||
dns["servers"] = dnsServers;
|
||||
dns["tag"] = "dns";
|
||||
result->coreConfig.insert("dns", dns);
|
||||
|
||||
// Routing
|
||||
QJsonObject routing;
|
||||
routing["domainStrategy"] = dataStore->domain_strategy;
|
||||
routing["domainMatcher"] = dataStore->domain_matcher == DomainMatcher::MPH ? "mph" : "linear";
|
||||
|
||||
// ip user rule
|
||||
QJsonObject routingRule_tmp;
|
||||
routingRule_tmp["type"] = "field";
|
||||
|
||||
// block
|
||||
routingRule_tmp["outboundTag"] = "block";
|
||||
for (const auto &line: SplitLines(dataStore->routing->block_ip)) {
|
||||
if (line.startsWith("#")) continue;
|
||||
status->ipListBlock += line;
|
||||
}
|
||||
// final add block route
|
||||
if (!status->ipListBlock.isEmpty()) {
|
||||
auto tmp = routingRule_tmp;
|
||||
tmp["ip"] = status->ipListBlock;
|
||||
status->routingRules += tmp;
|
||||
}
|
||||
if (!status->domainListBlock.isEmpty()) {
|
||||
auto tmp = routingRule_tmp;
|
||||
tmp["domain"] = status->domainListBlock;
|
||||
status->routingRules += tmp;
|
||||
}
|
||||
|
||||
// proxy
|
||||
routingRule_tmp["outboundTag"] = tagProxy;
|
||||
for (const auto &line: SplitLines(dataStore->routing->proxy_ip)) {
|
||||
if (line.startsWith("#")) continue;
|
||||
status->ipListRemote += line;
|
||||
}
|
||||
// final add proxy route
|
||||
if (!status->ipListRemote.isEmpty()) {
|
||||
auto tmp = routingRule_tmp;
|
||||
tmp["ip"] = status->ipListRemote;
|
||||
status->routingRules += tmp;
|
||||
}
|
||||
if (!status->domainListRemote.isEmpty()) {
|
||||
auto tmp = routingRule_tmp;
|
||||
tmp["domain"] = status->domainListRemote;
|
||||
status->routingRules += tmp;
|
||||
}
|
||||
|
||||
// bypass
|
||||
routingRule_tmp["outboundTag"] = "bypass";
|
||||
for (const auto &line: SplitLines(dataStore->routing->direct_ip)) {
|
||||
if (line.startsWith("#")) continue;
|
||||
status->ipListDirect += line;
|
||||
}
|
||||
// final add bypass route
|
||||
if (!status->ipListDirect.isEmpty()) {
|
||||
auto tmp = routingRule_tmp;
|
||||
tmp["ip"] = status->ipListDirect;
|
||||
status->routingRules += tmp;
|
||||
}
|
||||
if (!status->domainListDirect.isEmpty()) {
|
||||
auto tmp = routingRule_tmp;
|
||||
tmp["domain"] = status->domainListDirect;
|
||||
status->routingRules += tmp;
|
||||
}
|
||||
|
||||
// final add routing rule
|
||||
// custom routing rule
|
||||
auto routingRules = QString2QJsonObject(dataStore->routing->custom)["rules"].toArray();
|
||||
QJSONARRAY_ADD(routingRules, QString2QJsonObject(dataStore->custom_route_global)["rules"].toArray())
|
||||
QJSONARRAY_ADD(routingRules, status->routingRules)
|
||||
routing["rules"] = routingRules;
|
||||
result->coreConfig.insert("routing", routing);
|
||||
|
||||
// Policy & stats
|
||||
QJsonObject policy;
|
||||
QJsonObject levels;
|
||||
QJsonObject level1;
|
||||
level1["connIdle"] = 30;
|
||||
levels["1"] = level1;
|
||||
policy["levels"] = levels;
|
||||
|
||||
QJsonObject policySystem;
|
||||
policySystem["statsOutboundDownlink"] = true;
|
||||
policySystem["statsOutboundUplink"] = true;
|
||||
policy["system"] = policySystem;
|
||||
result->coreConfig.insert("policy", policy);
|
||||
result->coreConfig.insert("stats", QJsonObject());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString BuildChain(int chainId, const QList<QSharedPointer<ProxyEntity>> &ents,
|
||||
const QSharedPointer<BuildConfigStatus> &status) {
|
||||
QString chainTag = "c-" + Int2String(chainId);
|
||||
bool muxApplied = false;
|
||||
|
||||
QString pastTag;
|
||||
int index = 0;
|
||||
|
||||
for (const auto &ent: ents) {
|
||||
// tagOut: v2ray outbound tag for a profile
|
||||
// profile2 (in) (global) tag g-(id)
|
||||
// profile1 tag (chainTag)-(id)
|
||||
// profile0 (out) tag (chainTag)-(id) / single: chainTag=g-(id)
|
||||
auto tagOut = chainTag + "-" + Int2String(ent->id);
|
||||
|
||||
// needGlobal: can only contain one?
|
||||
bool needGlobal = false;
|
||||
|
||||
// first profile set as global
|
||||
if (index == ents.length() - 1) {
|
||||
needGlobal = true;
|
||||
tagOut = "g-" + Int2String(ent->id);
|
||||
}
|
||||
|
||||
if (needGlobal) {
|
||||
if (status->globalProfiles.contains(ent->id)) {
|
||||
continue;
|
||||
}
|
||||
status->globalProfiles += ent->id;
|
||||
}
|
||||
|
||||
if (index > 0) {
|
||||
// chain rules: past
|
||||
if (!ents[index - 1]->bean->NeedExternal()) {
|
||||
auto replaced = status->outbounds.last().toObject();
|
||||
replaced["proxySettings"] = QJsonObject{
|
||||
{"tag", tagOut},
|
||||
{"transportLayer", true},
|
||||
};
|
||||
status->outbounds.removeLast();
|
||||
status->outbounds += replaced;
|
||||
} else {
|
||||
status->routingRules += QJsonObject{
|
||||
{"type", "field"},
|
||||
{"inboundTag", QJsonArray{pastTag + "-mapping"}},
|
||||
{"outboundTag", tagOut},
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// index == 0 means last profile in chain / not chain
|
||||
chainTag = tagOut;
|
||||
status->result->outboundStat = ent->traffic_data;
|
||||
}
|
||||
|
||||
// chain rules: this
|
||||
auto mapping_port = MkPort();
|
||||
if (ent->bean->NeedExternal()) {
|
||||
status->inbounds += QJsonObject{
|
||||
{"protocol", "dokodemo-door"},
|
||||
{"tag", tagOut + "-mapping"},
|
||||
{"listen", "127.0.0.1"},
|
||||
{"port", mapping_port},
|
||||
{"settings", QJsonObject{ // to
|
||||
{"address", ent->bean->serverAddress},
|
||||
{"port", ent->bean->serverPort},
|
||||
{"network", "tcp,udp"},
|
||||
}},
|
||||
};
|
||||
// no chain rule and not outbound, so need to set to direct
|
||||
if (index == ents.length() - 1) {
|
||||
status->routingRules += QJsonObject{
|
||||
{"type", "field"},
|
||||
{"inboundTag", QJsonArray{tagOut + "-mapping"}},
|
||||
{"outboundTag", "direct"},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Outbound
|
||||
|
||||
QJsonObject outbound;
|
||||
fmt::CoreObjOutboundBuildResult coreR;
|
||||
fmt::ExternalBuildResult extR;
|
||||
|
||||
if (ent->bean->NeedExternal()) {
|
||||
auto ext_socks_port = MkPort();
|
||||
extR = ent->bean->BuildExternal(mapping_port, ext_socks_port);
|
||||
if (!extR.error.isEmpty()) { // rejected
|
||||
status->result->error = extR.error;
|
||||
return "";
|
||||
}
|
||||
|
||||
// SOCKS OUTBOUND
|
||||
outbound["protocol"] = "socks";
|
||||
QJsonObject settings;
|
||||
QJsonArray servers;
|
||||
QJsonObject server;
|
||||
server["address"] = "127.0.0.1";
|
||||
server["port"] = ext_socks_port;
|
||||
servers.push_back(server);
|
||||
settings["servers"] = servers;
|
||||
outbound["settings"] = settings;
|
||||
|
||||
// EXTERNAL PROCESS
|
||||
auto extC = new sys::ExternalProcess(ent->bean->DisplayType(),
|
||||
extR.program, extR.arguments, extR.env);
|
||||
status->result->ext += extC;
|
||||
} else {
|
||||
coreR = ent->bean->BuildCoreObj();
|
||||
if (!coreR.error.isEmpty()) { // rejected
|
||||
status->result->error = coreR.error;
|
||||
return "";
|
||||
}
|
||||
outbound = coreR.outbound;
|
||||
}
|
||||
|
||||
// outbound misc
|
||||
outbound["tag"] = tagOut;
|
||||
outbound["domainStrategy"] = dataStore->outbound_domain_strategy;
|
||||
ent->traffic_data->id = ent->id;
|
||||
ent->traffic_data->tag = tagOut.toStdString();
|
||||
status->result->outboundStats += ent->traffic_data;
|
||||
|
||||
// apply mux
|
||||
if (dataStore->mux_cool > 0 && !muxApplied) {
|
||||
// TODO refactor mux settings
|
||||
if (ent->type == "vmess" || ent->type == "trojan" || ent->type == "vless") {
|
||||
auto muxObj = QJsonObject{
|
||||
{"enabled", true},
|
||||
{"concurrency", dataStore->mux_cool},
|
||||
};
|
||||
auto stream = GetStreamSettings(ent->bean);
|
||||
if (stream != nullptr && !stream->packet_encoding.isEmpty()) {
|
||||
muxObj["packetEncoding"] = stream->packet_encoding;
|
||||
}
|
||||
outbound["mux"] = muxObj;
|
||||
muxApplied = true;
|
||||
}
|
||||
}
|
||||
|
||||
// apply custom outbound settings
|
||||
auto custom_item = ent->bean->_get("custom");
|
||||
if (custom_item != nullptr) {
|
||||
ApplyCustomOutboundJsonSettings(QString2QJsonObject(*((QString *) custom_item->ptr)), outbound);
|
||||
}
|
||||
|
||||
// Bypass Lookup for the first profile
|
||||
if (index == ents.length() - 1 && !IsIpAddress(ent->bean->serverAddress)) {
|
||||
if (dataStore->enhance_resolve_server_domain) {
|
||||
status->result->tryDomains += ent->bean->serverAddress;
|
||||
} else {
|
||||
status->domainListDNSDirect += "full:" + ent->bean->serverAddress;
|
||||
}
|
||||
}
|
||||
|
||||
status->outbounds += outbound;
|
||||
pastTag = tagOut;
|
||||
index++;
|
||||
}
|
||||
|
||||
// this is a chain
|
||||
if (ents.length() > 1) {
|
||||
// Chain ent traffic stat
|
||||
status->currentEnt->traffic_data->id = status->currentEnt->id;
|
||||
status->currentEnt->traffic_data->tag = chainTag.toStdString();
|
||||
status->result->outboundStats += status->currentEnt->traffic_data;
|
||||
}
|
||||
|
||||
return chainTag;
|
||||
}
|
||||
|
||||
}
|
||||
48
db/ConfigBuilder.hpp
Normal file
48
db/ConfigBuilder.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include "ProxyEntity.hpp"
|
||||
#include "sys/ExternalProcess.hpp"
|
||||
|
||||
namespace NekoRay {
|
||||
class BuildConfigResult {
|
||||
public:
|
||||
QString error;
|
||||
QJsonObject coreConfig;
|
||||
QStringList tryDomains;
|
||||
|
||||
QList<QSharedPointer<traffic::TrafficData>> outboundStats; // all, but not including "bypass" "block"
|
||||
QSharedPointer<traffic::TrafficData> outboundStat; // main
|
||||
|
||||
QList<sys::ExternalProcess *> ext;
|
||||
};
|
||||
|
||||
class BuildConfigStatus {
|
||||
public:
|
||||
QSharedPointer<BuildConfigResult> result;
|
||||
|
||||
QJsonArray domainListDNSRemote;
|
||||
QJsonArray domainListDNSDirect;
|
||||
QJsonArray domainListRemote;
|
||||
QJsonArray domainListDirect;
|
||||
QJsonArray ipListRemote;
|
||||
QJsonArray ipListDirect;
|
||||
|
||||
QJsonArray domainListBlock;
|
||||
QJsonArray ipListBlock;
|
||||
|
||||
QJsonArray routingRules;
|
||||
QJsonObject hosts;
|
||||
|
||||
QJsonArray inbounds;
|
||||
QJsonArray outbounds;
|
||||
|
||||
QList<int> globalProfiles;
|
||||
|
||||
ProxyEntity *currentEnt;
|
||||
};
|
||||
|
||||
QSharedPointer<BuildConfigResult> BuildConfig(const QSharedPointer<ProxyEntity> &ent, bool forTest);
|
||||
|
||||
QString BuildChain(int chainId, const QList<QSharedPointer<ProxyEntity>> &ents,
|
||||
const QSharedPointer<BuildConfigStatus> &status);
|
||||
}
|
||||
260
db/Database.cpp
Normal file
260
db/Database.cpp
Normal file
@@ -0,0 +1,260 @@
|
||||
#include "Database.hpp"
|
||||
|
||||
#include "fmt/includes.h"
|
||||
|
||||
#include <QFile>
|
||||
|
||||
namespace NekoRay {
|
||||
|
||||
ProfileManager *profileManager = new ProfileManager();
|
||||
|
||||
ProfileManager::ProfileManager() : JsonStore("groups/pm.json") {
|
||||
_hooks_after_load.push_back([=]() { LoadManager(); });
|
||||
_hooks_before_save.push_back([=]() { SaveManager(); });
|
||||
_add(new configItem("profiles", &_profiles, itemType::integerList));
|
||||
_add(new configItem("groups", &_groups, itemType::integerList));
|
||||
}
|
||||
|
||||
void ProfileManager::LoadManager() {
|
||||
for (auto id: _profiles) {
|
||||
profiles[id] = LoadProxyEntity(QString("profiles/%1.json").arg(id));
|
||||
}
|
||||
for (auto id: _groups) {
|
||||
groups[id] = LoadGroup(QString("groups/%1.json").arg(id));
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileManager::SaveManager() {
|
||||
}
|
||||
|
||||
QSharedPointer<ProxyEntity> ProfileManager::LoadProxyEntity(const QString &jsonPath) {
|
||||
// Load type
|
||||
ProxyEntity ent0(nullptr, nullptr);
|
||||
ent0.fn = jsonPath;
|
||||
auto validJson = ent0.Load();
|
||||
auto type = ent0.type;
|
||||
|
||||
// Load content
|
||||
QSharedPointer<ProxyEntity> ent;
|
||||
bool validType = validJson;
|
||||
|
||||
if (validType) {
|
||||
ent = NewProxyEntity(type);
|
||||
validType = ent->bean->version != -114514;
|
||||
}
|
||||
|
||||
if (validType) {
|
||||
// 加载前设置好 fn
|
||||
ent->load_control_force = true;
|
||||
ent->fn = jsonPath;
|
||||
ent->Load();
|
||||
return ent;
|
||||
} else {
|
||||
// 返回一个假的?
|
||||
ent->bean->name = "[Load Error]";
|
||||
return ent;
|
||||
}
|
||||
}
|
||||
|
||||
// 新建的不给 fn 和 id
|
||||
|
||||
QSharedPointer<ProxyEntity> ProfileManager::NewProxyEntity(const QString &type) {
|
||||
fmt::AbstractBean *bean;
|
||||
|
||||
if (type == "socks") {
|
||||
bean = new fmt::SocksHttpBean(NekoRay::fmt::SocksHttpBean::type_Socks5);
|
||||
} else if (type == "http") {
|
||||
bean = new fmt::SocksHttpBean(NekoRay::fmt::SocksHttpBean::type_HTTP);
|
||||
} else if (type == "shadowsocks") {
|
||||
bean = new fmt::ShadowSocksBean();
|
||||
} else if (type == "chain") {
|
||||
bean = new fmt::ChainBean();
|
||||
} else if (type == "vmess") {
|
||||
bean = new fmt::VMessBean();
|
||||
} else if (type == "trojan") {
|
||||
bean = new fmt::TrojanVLESSBean(fmt::TrojanVLESSBean::proxy_Trojan);
|
||||
} else if (type == "vless") {
|
||||
bean = new fmt::TrojanVLESSBean(fmt::TrojanVLESSBean::proxy_VLESS);
|
||||
} else if (type == "naive") {
|
||||
bean = new fmt::NaiveBean();
|
||||
} else if (type == "custom") {
|
||||
bean = new fmt::CustomBean();
|
||||
} else {
|
||||
bean = new fmt::AbstractBean(-114514);
|
||||
}
|
||||
|
||||
auto ent = QSharedPointer<ProxyEntity>(new ProxyEntity(bean, type));
|
||||
return ent;
|
||||
}
|
||||
|
||||
QSharedPointer<Group> ProfileManager::NewGroup() {
|
||||
auto ent = QSharedPointer<Group>(new Group());
|
||||
return ent;
|
||||
}
|
||||
|
||||
// ProxyEntity
|
||||
|
||||
ProxyEntity::ProxyEntity(fmt::AbstractBean *bean, QString _type) {
|
||||
type = std::move(_type);
|
||||
_add(new configItem("type", &type, itemType::string));
|
||||
_add(new configItem("id", &id, itemType::integer));
|
||||
_add(new configItem("gid", &gid, itemType::integer));
|
||||
|
||||
// 可以不关联 bean,只加载 ProxyEntity 的信息
|
||||
if (bean != nullptr) {
|
||||
this->bean = QSharedPointer<fmt::AbstractBean>(bean);
|
||||
// 有虚函数就要在这里 dynamic_cast
|
||||
_add(new configItem("bean", dynamic_cast<JsonStore *>(bean), itemType::jsonStore));
|
||||
_add(new configItem("traffic", dynamic_cast<JsonStore *>(traffic_data.get()), itemType::jsonStore));
|
||||
}
|
||||
};
|
||||
|
||||
QString ProxyEntity::DisplayLatency() const {
|
||||
if (latency < 0) {
|
||||
return QObject::tr("Unavailable");
|
||||
} else if (latency > 0) {
|
||||
return QString("%1 ms").arg(latency);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Profile
|
||||
|
||||
int ProfileManager::NewProfileID() const {
|
||||
if (profiles.empty()) { return 0; } else { return profiles.lastKey() + 1; }
|
||||
}
|
||||
|
||||
bool ProfileManager::AddProfile(const QSharedPointer<ProxyEntity> &ent, int gid) {
|
||||
if (ent->id >= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ent->gid = gid < 0 ? dataStore->current_group : gid;
|
||||
ent->id = NewProfileID();
|
||||
profiles[ent->id] = ent;
|
||||
_profiles.push_back(ent->id);
|
||||
Save();
|
||||
|
||||
ent->fn = QString("profiles/%1.json").arg(ent->id);
|
||||
ent->Save();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProfileManager::DeleteProfile(int id) {
|
||||
if (id < 0) return;
|
||||
if (dataStore->started_id == id) return;
|
||||
profiles.remove(id);
|
||||
_profiles.removeAll(id);
|
||||
Save();
|
||||
QFile(QString("profiles/%1.json").arg(id)).remove();
|
||||
}
|
||||
|
||||
void ProfileManager::MoveProfile(const QSharedPointer<ProxyEntity> &ent, int gid) {
|
||||
if (gid == ent->gid || gid < 0) return;
|
||||
auto oldGroup = GetGroup(ent->gid);
|
||||
if (oldGroup != nullptr && !oldGroup->order.isEmpty()) {
|
||||
oldGroup->order.removeAll(ent->id);
|
||||
oldGroup->Save();
|
||||
}
|
||||
auto newGroup = GetGroup(gid);
|
||||
if (newGroup != nullptr && !newGroup->order.isEmpty()) {
|
||||
newGroup->order.push_back(ent->id);
|
||||
newGroup->Save();
|
||||
}
|
||||
ent->gid = gid;
|
||||
ent->Save();
|
||||
}
|
||||
|
||||
QSharedPointer<ProxyEntity> ProfileManager::GetProfile(int id) {
|
||||
if (profiles.contains(id)) {
|
||||
return profiles[id];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//Group
|
||||
|
||||
Group::Group() {
|
||||
_add(new configItem("id", &id, itemType::integer));
|
||||
_add(new configItem("archive", &archive, itemType::boolean));
|
||||
_add(new configItem("name", &name, itemType::string));
|
||||
_add(new configItem("order", &order, itemType::integerList));
|
||||
_add(new configItem("url", &url, itemType::string));
|
||||
_add(new configItem("info", &info, itemType::string));
|
||||
}
|
||||
|
||||
QSharedPointer<Group> ProfileManager::LoadGroup(const QString &jsonPath) {
|
||||
QSharedPointer<Group> ent = QSharedPointer<Group>(new Group());
|
||||
ent->fn = jsonPath;
|
||||
ent->Load();
|
||||
return ent;
|
||||
}
|
||||
|
||||
int ProfileManager::NewGroupID() const {
|
||||
if (groups.empty()) { return 0; } else { return groups.lastKey() + 1; }
|
||||
}
|
||||
|
||||
bool ProfileManager::AddGroup(const QSharedPointer<Group> &ent) {
|
||||
if (ent->id >= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ent->id = NewGroupID();
|
||||
groups[ent->id] = ent;
|
||||
_groups.push_back(ent->id);
|
||||
Save();
|
||||
|
||||
ent->fn = QString("groups/%1.json").arg(ent->id);
|
||||
ent->Save();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProfileManager::DeleteGroup(int gid) {
|
||||
if (groups.count() == 1) return;
|
||||
QList<int> toDelete;
|
||||
for (const auto &profile: profiles) {
|
||||
if (profile->gid == gid) toDelete += profile->id; // map访问中,不能操作
|
||||
}
|
||||
for (const auto &id: toDelete) {
|
||||
DeleteProfile(id);
|
||||
}
|
||||
groups.remove(gid);
|
||||
_groups.removeAll(gid);
|
||||
Save();
|
||||
QFile(QString("groups/%1.json").arg(gid)).remove();
|
||||
}
|
||||
|
||||
QSharedPointer<Group> ProfileManager::GetGroup(int id) {
|
||||
if (groups.contains(id)) {
|
||||
return groups[id];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QSharedPointer<Group> ProfileManager::CurrentGroup() {
|
||||
return GetGroup(NekoRay::dataStore->current_group);
|
||||
}
|
||||
|
||||
QList<QSharedPointer<ProxyEntity>> Group::Profiles() const {
|
||||
QList<QSharedPointer<ProxyEntity>> ret;
|
||||
for (const auto &ent: profileManager->profiles) {
|
||||
if (id == ent->gid) ret += ent;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
QList<QSharedPointer<ProxyEntity>> Group::ProfilesWithOrder() const {
|
||||
if (order.isEmpty()) {
|
||||
return Profiles();
|
||||
} else {
|
||||
QList<QSharedPointer<ProxyEntity>> ret;
|
||||
for (auto _id: order) {
|
||||
auto ent = profileManager->GetProfile(_id);
|
||||
if (ent != nullptr) ret += ent;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
54
db/Database.hpp
Normal file
54
db/Database.hpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "main/NekoRay.hpp"
|
||||
#include "ProxyEntity.hpp"
|
||||
#include "Group.hpp"
|
||||
|
||||
namespace NekoRay {
|
||||
class ProfileManager : public JsonStore {
|
||||
public:
|
||||
QMap<int, QSharedPointer<ProxyEntity>> profiles;
|
||||
QMap<int, QSharedPointer<Group>> groups;
|
||||
|
||||
// JSON
|
||||
QList<int> _profiles;
|
||||
QList<int> _groups; // with order
|
||||
|
||||
ProfileManager();
|
||||
|
||||
[[nodiscard]] static QSharedPointer<ProxyEntity> NewProxyEntity(const QString &type);
|
||||
|
||||
[[nodiscard]] static QSharedPointer<Group> NewGroup();
|
||||
|
||||
bool AddProfile(const QSharedPointer<ProxyEntity> &ent, int gid = -1);
|
||||
|
||||
void DeleteProfile(int id);
|
||||
|
||||
void MoveProfile(const QSharedPointer<ProxyEntity> &ent, int gid);
|
||||
|
||||
QSharedPointer<ProxyEntity> GetProfile(int id);
|
||||
|
||||
bool AddGroup(const QSharedPointer<Group> &ent);
|
||||
|
||||
void DeleteGroup(int gid);
|
||||
|
||||
QSharedPointer<Group> GetGroup(int id);
|
||||
|
||||
QSharedPointer<Group> CurrentGroup();
|
||||
|
||||
private:
|
||||
void LoadManager();
|
||||
|
||||
void SaveManager();
|
||||
|
||||
[[nodiscard]] int NewProfileID() const;
|
||||
|
||||
[[nodiscard]] int NewGroupID() const;
|
||||
|
||||
static QSharedPointer<ProxyEntity> LoadProxyEntity(const QString &jsonPath);
|
||||
|
||||
static QSharedPointer<Group> LoadGroup(const QString &jsonPath);
|
||||
};
|
||||
|
||||
extern ProfileManager *profileManager;
|
||||
}
|
||||
24
db/Group.hpp
Normal file
24
db/Group.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "main/NekoRay.hpp"
|
||||
#include "ProxyEntity.hpp"
|
||||
|
||||
namespace NekoRay {
|
||||
class Group : public JsonStore {
|
||||
public:
|
||||
int id = -1;
|
||||
bool archive = false;
|
||||
QString name = "";
|
||||
QList<int> order;
|
||||
QString url = "";
|
||||
QString info = "";
|
||||
|
||||
Group();
|
||||
|
||||
// 按 id 顺序
|
||||
[[nodiscard]] QList<QSharedPointer<ProxyEntity>> Profiles() const;
|
||||
|
||||
// 按 显示 顺序
|
||||
[[nodiscard]] QList<QSharedPointer<ProxyEntity>> ProfilesWithOrder() const;
|
||||
};
|
||||
}
|
||||
77
db/ProfileFilter.cpp
Normal file
77
db/ProfileFilter.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "ProfileFilter.hpp"
|
||||
|
||||
namespace NekoRay {
|
||||
void ProfileFilter::Uniq(const QList<QSharedPointer<ProxyEntity>> &in,
|
||||
QList<QSharedPointer<ProxyEntity>> &out,
|
||||
bool by_address, bool keep_last) {
|
||||
QMap<QString, QSharedPointer<ProxyEntity>> hashMap;
|
||||
|
||||
for (const auto &ent: in) {
|
||||
QString key = by_address ? (ent->bean->DisplayAddress() + ent->bean->DisplayType())
|
||||
: ent->bean->ToJsonBytes();
|
||||
if (hashMap.contains(key)) {
|
||||
if (keep_last) {
|
||||
out.removeAll(hashMap[key]);
|
||||
hashMap[key] = ent;
|
||||
out += ent;
|
||||
}
|
||||
} else {
|
||||
hashMap[key] = ent;
|
||||
out += ent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ProfileFilter::Common(const QList<QSharedPointer<ProxyEntity>> &src,
|
||||
const QList<QSharedPointer<ProxyEntity>> &dst,
|
||||
QList<QSharedPointer<ProxyEntity >> &out,
|
||||
bool by_address, bool keep_last) {
|
||||
QMap<QString, QSharedPointer<ProxyEntity>> hashMap;
|
||||
|
||||
for (const auto &ent: src) {
|
||||
QString key = by_address ? (ent->bean->DisplayAddress() + ent->bean->DisplayType())
|
||||
: ent->bean->ToJsonBytes();
|
||||
hashMap[key] = ent;
|
||||
}
|
||||
for (const auto &ent: dst) {
|
||||
QString key = by_address ? (ent->bean->DisplayAddress() + ent->bean->DisplayType())
|
||||
: ent->bean->ToJsonBytes();
|
||||
if (hashMap.contains(key)) {
|
||||
if (keep_last) {
|
||||
out += ent;
|
||||
} else {
|
||||
out += hashMap[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileFilter::OnlyInSrc(const QList<QSharedPointer<ProxyEntity>> &src,
|
||||
const QList<QSharedPointer<ProxyEntity>> &dst,
|
||||
QList<QSharedPointer<ProxyEntity>> &out,
|
||||
bool by_address) {
|
||||
QMap<QString, bool> hashMap;
|
||||
|
||||
for (const auto &ent: dst) {
|
||||
QString key = by_address ? (ent->bean->DisplayAddress() + ent->bean->DisplayType())
|
||||
: ent->bean->ToJsonBytes();
|
||||
hashMap[key] = true;
|
||||
}
|
||||
for (const auto &ent: src) {
|
||||
QString key = by_address ? (ent->bean->DisplayAddress() + ent->bean->DisplayType())
|
||||
: ent->bean->ToJsonBytes();
|
||||
if (!hashMap.contains(key)) out += ent;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ProfileFilter::OnlyInSrc_ByPointer(const QList<QSharedPointer<ProxyEntity>> &src,
|
||||
const QList<QSharedPointer<ProxyEntity>> &dst,
|
||||
QList<QSharedPointer<ProxyEntity>> &out) {
|
||||
for (const auto &ent: src) {
|
||||
if (!dst.contains(ent)) out += ent;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
36
db/ProfileFilter.hpp
Normal file
36
db/ProfileFilter.hpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "ProxyEntity.hpp"
|
||||
|
||||
namespace NekoRay {
|
||||
class ProfileFilter {
|
||||
public:
|
||||
static void Uniq(
|
||||
const QList<QSharedPointer<ProxyEntity>> &in,
|
||||
QList<QSharedPointer<ProxyEntity>> &out,
|
||||
bool by_address = false, //def by bean
|
||||
bool keep_last = false //def keep first
|
||||
);
|
||||
|
||||
static void Common(
|
||||
const QList<QSharedPointer<ProxyEntity>> &src,
|
||||
const QList<QSharedPointer<ProxyEntity>> &dst,
|
||||
QList<QSharedPointer<ProxyEntity>> &out,
|
||||
bool by_address = false, //def by bean
|
||||
bool keep_last = false //def keep first
|
||||
);
|
||||
|
||||
static void OnlyInSrc(
|
||||
const QList<QSharedPointer<ProxyEntity>> &src,
|
||||
const QList<QSharedPointer<ProxyEntity>> &dst,
|
||||
QList<QSharedPointer<NekoRay::ProxyEntity>> &out,
|
||||
bool by_address = false //def by bean
|
||||
);
|
||||
|
||||
static void OnlyInSrc_ByPointer(
|
||||
const QList<QSharedPointer<ProxyEntity>> &src,
|
||||
const QList<QSharedPointer<ProxyEntity>> &dst,
|
||||
QList<QSharedPointer<ProxyEntity>> &out
|
||||
);
|
||||
};
|
||||
}
|
||||
71
db/ProxyEntity.hpp
Normal file
71
db/ProxyEntity.hpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include "main/NekoRay.hpp"
|
||||
#include "TrafficData.hpp"
|
||||
#include "fmt/AbstractBean.hpp"
|
||||
|
||||
namespace NekoRay {
|
||||
namespace fmt {
|
||||
class SocksHttpBean;
|
||||
|
||||
class ShadowSocksBean;
|
||||
|
||||
class VMessBean;
|
||||
|
||||
class TrojanVLESSBean;
|
||||
|
||||
class NaiveBean;
|
||||
|
||||
class CustomBean;
|
||||
|
||||
class ChainBean;
|
||||
};
|
||||
|
||||
class ProxyEntity : public JsonStore {
|
||||
public:
|
||||
QString type;
|
||||
|
||||
int id = -1;
|
||||
int gid = 0;
|
||||
QSharedPointer<fmt::AbstractBean> bean;
|
||||
QSharedPointer<traffic::TrafficData> traffic_data = QSharedPointer<traffic::TrafficData>(
|
||||
new traffic::TrafficData(""));
|
||||
|
||||
// Cache
|
||||
int latency = 0;
|
||||
QString full_test_report;
|
||||
|
||||
ProxyEntity(fmt::AbstractBean *bean, QString _type);
|
||||
|
||||
[[nodiscard]] QString DisplayLatency() const;
|
||||
|
||||
[[nodiscard]] fmt::ChainBean *ChainBean() const {
|
||||
return (fmt::ChainBean *) bean.get();
|
||||
};
|
||||
|
||||
[[nodiscard]] fmt::SocksHttpBean *SocksHTTPBean() const {
|
||||
return (fmt::SocksHttpBean *) bean.get();
|
||||
};
|
||||
|
||||
[[nodiscard]] fmt::ShadowSocksBean *ShadowSocksBean() const {
|
||||
return (fmt::ShadowSocksBean *) bean.get();
|
||||
};
|
||||
|
||||
[[nodiscard]] fmt::VMessBean *VMessBean() const {
|
||||
return (fmt::VMessBean *) bean.get();
|
||||
};
|
||||
|
||||
[[nodiscard]] fmt::TrojanVLESSBean *TrojanVLESSBean() const {
|
||||
return (fmt::TrojanVLESSBean *) bean.get();
|
||||
};
|
||||
|
||||
[[nodiscard]] fmt::NaiveBean *NaiveBean() const {
|
||||
return (fmt::NaiveBean *) bean.get();
|
||||
};
|
||||
|
||||
[[nodiscard]] fmt::CustomBean *CustomBean() const {
|
||||
return (fmt::CustomBean *) bean.get();
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
38
db/TrafficData.hpp
Normal file
38
db/TrafficData.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "main/NekoRay.hpp"
|
||||
|
||||
namespace NekoRay::traffic {
|
||||
class TrafficData : public JsonStore {
|
||||
public:
|
||||
int id = -1; // ent id
|
||||
std::string tag;
|
||||
|
||||
long long downlink = 0;
|
||||
long long uplink = 0;
|
||||
long long downlink_rate = 0;
|
||||
long long uplink_rate = 0;
|
||||
|
||||
explicit TrafficData(std::string tag) {
|
||||
this->tag = std::move(tag);
|
||||
_add(new configItem("dl", &downlink, itemType::integer64));
|
||||
_add(new configItem("ul", &uplink, itemType::integer64));
|
||||
};
|
||||
|
||||
void Reset() {
|
||||
downlink = 0;
|
||||
uplink = 0;
|
||||
downlink_rate = 0;
|
||||
uplink_rate = 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] QString DisplaySpeed() const {
|
||||
return QString("%1↑ %2↓").arg(ReadableSize(uplink_rate), ReadableSize(downlink_rate));
|
||||
}
|
||||
|
||||
[[nodiscard]] QString DisplayTraffic() const {
|
||||
if (downlink + uplink == 0) return "";
|
||||
return QString("%1↑ %2↓").arg(ReadableSize(uplink), ReadableSize(downlink));
|
||||
}
|
||||
};
|
||||
}
|
||||
120
db/TrafficLooper.cpp
Normal file
120
db/TrafficLooper.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
#include "TrafficLooper.hpp"
|
||||
|
||||
#include "rpc/gRPC.h"
|
||||
#include "ui/mainwindow.h"
|
||||
|
||||
#include <QThread>
|
||||
|
||||
namespace NekoRay::traffic {
|
||||
|
||||
TrafficLooper *trafficLooper = new TrafficLooper;
|
||||
|
||||
std::unique_ptr<TrafficData> TrafficLooper::update_stats(TrafficData *item) {
|
||||
#ifndef NKR_NO_GRPC
|
||||
auto uplink = NekoRay::rpc::defaultClient->QueryStats(item->tag, "uplink");
|
||||
auto downlink = NekoRay::rpc::defaultClient->QueryStats(item->tag, "downlink");
|
||||
|
||||
item->downlink += downlink;
|
||||
item->uplink += uplink;
|
||||
|
||||
//?
|
||||
item->downlink_rate = downlink * 1000 / dataStore->traffic_loop_interval;
|
||||
item->uplink_rate = uplink * 1000 / dataStore->traffic_loop_interval;
|
||||
|
||||
// return diff
|
||||
auto ret = std::make_unique<TrafficData>(item->tag);
|
||||
ret->downlink = downlink;
|
||||
ret->uplink = uplink;
|
||||
ret->downlink_rate = item->downlink_rate;
|
||||
ret->uplink_rate = item->uplink_rate;
|
||||
return ret;
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QJsonArray TrafficLooper::get_connection_list() {
|
||||
#ifndef NKR_NO_GRPC
|
||||
auto str = NekoRay::rpc::defaultClient->ListV2rayConnections();
|
||||
QJsonDocument jsonDocument = QJsonDocument::fromJson(str.c_str());
|
||||
return jsonDocument.array();
|
||||
#else
|
||||
return QJsonArray{};
|
||||
#endif
|
||||
}
|
||||
|
||||
void TrafficLooper::update_all() {
|
||||
std::map<std::string, std::unique_ptr<TrafficData>> updated; // tag to diff
|
||||
for (const auto &item: items) {
|
||||
auto data = item.get();
|
||||
auto diff = std::move(updated[data->tag]);
|
||||
// 避免重复查询一个 outbound tag
|
||||
if (diff == nullptr) {
|
||||
diff = update_stats(data);
|
||||
updated[data->tag] = std::move(diff);
|
||||
} else {
|
||||
data->uplink += diff->uplink;
|
||||
data->downlink += diff->downlink;
|
||||
data->uplink_rate = diff->uplink_rate;
|
||||
data->downlink_rate = diff->downlink_rate;
|
||||
}
|
||||
}
|
||||
update_stats(bypass);
|
||||
}
|
||||
|
||||
[[noreturn]] void TrafficLooper::loop() {
|
||||
while (true) {
|
||||
auto sleep_ms = dataStore->traffic_loop_interval;
|
||||
auto user_disabled = sleep_ms == 0;
|
||||
if (sleep_ms < 500 || sleep_ms > 2000) sleep_ms = 1000;
|
||||
QThread::msleep(sleep_ms);
|
||||
if (user_disabled) continue;
|
||||
|
||||
if (!loop_enabled) {
|
||||
// 停止
|
||||
if (looping) {
|
||||
looping = false;
|
||||
runOnUiThread([=] {
|
||||
auto m = GetMainWindow();
|
||||
m->refresh_status("STOP");
|
||||
});
|
||||
}
|
||||
continue;
|
||||
} else {
|
||||
//开始
|
||||
if (!looping) {
|
||||
looping = true;
|
||||
}
|
||||
}
|
||||
|
||||
// do update
|
||||
loop_mutex.lock();
|
||||
|
||||
update_all();
|
||||
|
||||
// do conn list update
|
||||
QJsonArray conn_list;
|
||||
if (dataStore->connection_statistics) {
|
||||
conn_list = get_connection_list();
|
||||
}
|
||||
|
||||
loop_mutex.unlock();
|
||||
|
||||
// post to UI
|
||||
runOnUiThread([=] {
|
||||
auto m = GetMainWindow();
|
||||
if (proxy != nullptr) {
|
||||
m->refresh_status(
|
||||
QObject::tr("Proxy: %1\nDirect: %2").arg(proxy->DisplaySpeed(), bypass->DisplaySpeed()));
|
||||
}
|
||||
for (const auto &item: items) {
|
||||
if (item->id < 0) continue;
|
||||
m->refresh_proxy_list(item->id);
|
||||
}
|
||||
if (dataStore->connection_statistics) {
|
||||
m->refresh_connection_list(conn_list);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
32
db/TrafficLooper.hpp
Normal file
32
db/TrafficLooper.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <QSharedPointer>
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
#include <QMutex>
|
||||
|
||||
#include "TrafficData.hpp"
|
||||
|
||||
namespace NekoRay::traffic {
|
||||
class TrafficLooper {
|
||||
public:
|
||||
bool loop_enabled = false;
|
||||
bool looping = false;
|
||||
QMutex loop_mutex;
|
||||
|
||||
QList<QSharedPointer<TrafficData>> items;
|
||||
TrafficData *bypass = new TrafficData("bypass");
|
||||
TrafficData *proxy = nullptr;
|
||||
|
||||
static std::unique_ptr<TrafficData> update_stats(TrafficData *item);
|
||||
|
||||
static QJsonArray get_connection_list();
|
||||
|
||||
void update_all();
|
||||
|
||||
[[noreturn]] void loop();
|
||||
};
|
||||
|
||||
extern TrafficLooper *trafficLooper;
|
||||
}
|
||||
|
||||
1
examples/.gitignore
vendored
Normal file
1
examples/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
tun2socks
|
||||
5
examples/build-alpine.md
Normal file
5
examples/build-alpine.md
Normal file
@@ -0,0 +1,5 @@
|
||||
alpine 3.16
|
||||
|
||||
all use package
|
||||
|
||||
apk add git cmake g++ ninja zxing-cpp-dev yaml-cpp-dev grpc-dev protobuf-dev qt5-qtbase-dev qt5-qtsvg-dev qt5-qttools-dev qt5-qtx11extras-dev c-ares-dev re2-dev
|
||||
30
examples/netns-root.sh
Executable file
30
examples/netns-root.sh
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
set -x
|
||||
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Please run as root"
|
||||
exit
|
||||
fi
|
||||
|
||||
# add netns
|
||||
ip netns add nekoray
|
||||
# ip netns exec nekoray readlink /proc/self/ns/net
|
||||
|
||||
# add lo: lo is not shared
|
||||
ip -n nekoray addr add 127.0.0.1/8 dev lo
|
||||
ip -n nekoray link set dev lo up
|
||||
|
||||
# add tun
|
||||
ip -n nekoray tuntap add tun0 user $USERID mode tun
|
||||
ip -n nekoray addr add 26.0.0.1/30 dev tun0
|
||||
ip -n nekoray link set dev tun0 up
|
||||
ip -n nekoray route add default dev tun0
|
||||
|
||||
# set veth to use the socks port
|
||||
ip link add dev nekoray-ve1 type veth peer name nekoray-ve2
|
||||
ip addr add 26.1.0.1/30 dev nekoray-ve1
|
||||
ip link set nekoray-ve1 up
|
||||
ip link set nekoray-ve2 netns nekoray
|
||||
ip -n nekoray addr add 26.1.0.2/30 dev nekoray-ve2
|
||||
ip -n nekoray link set nekoray-ve2 up
|
||||
13
examples/netns.sh
Executable file
13
examples/netns.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
set -x
|
||||
|
||||
BASEDIR=$(dirname "$0")
|
||||
|
||||
# netns
|
||||
[ -f /var/run/netns/nekoray ] || pkexec env USERID=`id -u` sh -c "cd $PWD && $BASEDIR/netns-root.sh" || true
|
||||
|
||||
# run xjasonlyu/tun2socks to provide vpn
|
||||
firejail --noprofile --netns=nekoray ./tun2socks -device tun0 -proxy socks5://26.1.0.1:2080 -interface nekoray-ve2 -drop-multicast
|
||||
|
||||
# use "firejail --noprofile --netns=nekoray ..." to run your program in VPN
|
||||
9
examples/readme.txt
Normal file
9
examples/readme.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
Linux Only
|
||||
|
||||
此处为配置 VPN 的脚本,仅供参考,使用时要按实际情况替换某些参数(如 socks 端口)
|
||||
|
||||
vpn.sh 配置全局 VPN
|
||||
ctrl-c 退出后自动删除 VPN
|
||||
|
||||
vpn-netns.sh 配置 netns
|
||||
分应用代理,用法参考脚本内容
|
||||
14
examples/set-cap.sh
Executable file
14
examples/set-cap.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
set -x
|
||||
|
||||
if [ "$EUID" -ne 0 ]
|
||||
then echo "Please run as root"
|
||||
exit
|
||||
fi
|
||||
|
||||
killall nekoray_core || true
|
||||
cp nekoray_core /opt/nekoray_core
|
||||
cp geo* /opt/
|
||||
setcap cap_net_admin+ep /opt/nekoray_core
|
||||
ln -sf /opt/nekoray_core nekoray_core_cap
|
||||
43
examples/sing-box-vpn.json
Normal file
43
examples/sing-box-vpn.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"dns": {
|
||||
"servers": [],
|
||||
"rules": [],
|
||||
"strategy": "ipv4_only"
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"interface_name": "nekoray-tun",
|
||||
"inet4_address": "172.19.0.1/30",
|
||||
"auto_route": true,
|
||||
"sniff": false
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "socks",
|
||||
"tag": "nekoray-socks",
|
||||
"server": "127.0.0.1",
|
||||
"server_port": %PORT%
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"tag": "block"
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"network": "udp",
|
||||
"port": [
|
||||
135,
|
||||
137,
|
||||
138,
|
||||
139,
|
||||
5353
|
||||
],
|
||||
"outbound": "block"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
71
examples/vpn-run-root.sh
Executable file
71
examples/vpn-run-root.sh
Executable file
@@ -0,0 +1,71 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
set -x
|
||||
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Please run as root"
|
||||
exit
|
||||
fi
|
||||
|
||||
[ -z $PORT ] && echo "Please set env PORT" && exit
|
||||
[ -z $TABLE_FWMARK ] && echo "Please set env TABLE_FWMARK" && exit
|
||||
[ -z $TUN_NAME ] && echo "Please set env TUN_NAME" && exit
|
||||
[ -z $USER_ID ] && echo "Please set env USER_ID" && exit
|
||||
command -v pkill >/dev/null 2>&1 || exit
|
||||
|
||||
BASEDIR=$(dirname "$0")
|
||||
cd $BASEDIR
|
||||
|
||||
start() {
|
||||
# add tun (TODO the ip must be the same as matsuri)
|
||||
ip tuntap add $TUN_NAME mode tun user $USER_ID || return
|
||||
ip addr add 172.19.0.1/30 dev $TUN_NAME || return
|
||||
ip link set dev $TUN_NAME up || return
|
||||
|
||||
# set ipv4 rule
|
||||
ip rule add table $TABLE_FWMARK || return
|
||||
ip route add table $TABLE_FWMARK default dev $TUN_NAME || return
|
||||
|
||||
# set ipv6 unreachable
|
||||
ip -6 rule add table $TABLE_FWMARK || return
|
||||
ip -6 route add table $TABLE_FWMARK unreachable default || return
|
||||
|
||||
# set bypass: fwmark
|
||||
ip rule add fwmark $TABLE_FWMARK table main || return
|
||||
ip -6 rule add fwmark $TABLE_FWMARK table main || return
|
||||
|
||||
# set bypass: LAN
|
||||
for local in $BYPASS_IPS; do
|
||||
ip rule add to $local table main
|
||||
done
|
||||
|
||||
if [ ! -z $USE_NEKORAY ]; then
|
||||
"./nekoray_core" tool protect --protect-listen-path "$PROTECT_LISTEN_PATH" --protect-fwmark $TABLE_FWMARK
|
||||
else
|
||||
if [ -z "$PROTECT_LISTEN_PATH" ]; then
|
||||
"./tun2socks" -device $TUN_NAME -proxy socks5://127.0.0.1:$PORT -interface lo
|
||||
else
|
||||
"./tun2socks" -device $TUN_NAME -proxy socks5://127.0.0.1:$PORT -interface lo --protect-listen-path "$PROTECT_LISTEN_PATH" --protect-fwmark $TABLE_FWMARK
|
||||
rm "$PROTECT_LISTEN_PATH"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
stop() {
|
||||
for local in $BYPASS_IPS; do
|
||||
ip rule del to $local table main
|
||||
done
|
||||
ip rule del table $TABLE_FWMARK
|
||||
ip rule del fwmark $TABLE_FWMARK
|
||||
ip route del table $TABLE_FWMARK default
|
||||
ip -6 rule del table $TABLE_FWMARK
|
||||
ip -6 rule del fwmark $TABLE_FWMARK
|
||||
ip -6 route del table $TABLE_FWMARK default
|
||||
ip link del $TUN_NAME
|
||||
}
|
||||
|
||||
if [ "$1" != "stop" ]; then
|
||||
start || true
|
||||
fi
|
||||
|
||||
stop || true
|
||||
36
fmt/AbstractBean.cpp
Normal file
36
fmt/AbstractBean.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#include "AbstractBean.hpp"
|
||||
|
||||
namespace NekoRay::fmt {
|
||||
AbstractBean::AbstractBean(int version) {
|
||||
this->version = version;
|
||||
_add(new configItem("_v", &this->version, itemType::integer));
|
||||
_add(new configItem("name", &name, itemType::string));
|
||||
_add(new configItem("addr", &serverAddress, itemType::string));
|
||||
_add(new configItem("port", &serverPort, itemType::integer));
|
||||
}
|
||||
|
||||
QString AbstractBean::ToNekorayShareLink(const QString &type) {
|
||||
auto b = ToJson();
|
||||
QUrl url;
|
||||
url.setScheme("nekoray");
|
||||
url.setHost(type);
|
||||
url.setFragment(QJsonObject2QString(b, true)
|
||||
.toUtf8().toBase64(QByteArray::Base64UrlEncoding));
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
QString AbstractBean::DisplayAddress() {
|
||||
return ::DisplayAddress(serverAddress, serverPort);
|
||||
}
|
||||
|
||||
QString AbstractBean::DisplayName() {
|
||||
if (name.isEmpty()) {
|
||||
return DisplayAddress();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
QString AbstractBean::DisplayTypeAndName() {
|
||||
return QString(" [%1] %2").arg(DisplayType(), DisplayName());
|
||||
}
|
||||
}
|
||||
54
fmt/AbstractBean.hpp
Normal file
54
fmt/AbstractBean.hpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "main/NekoRay.hpp"
|
||||
|
||||
namespace NekoRay::fmt {
|
||||
|
||||
struct CoreObjOutboundBuildResult {
|
||||
public:
|
||||
QJsonObject outbound;
|
||||
QString error;
|
||||
};
|
||||
|
||||
struct ExternalBuildResult {
|
||||
public:
|
||||
QString program;
|
||||
QStringList env;
|
||||
QStringList arguments;
|
||||
QString error;
|
||||
};
|
||||
|
||||
class AbstractBean : public JsonStore {
|
||||
public:
|
||||
int version;
|
||||
QString name = "";
|
||||
QString serverAddress = "127.0.0.1";
|
||||
int serverPort = 1080;
|
||||
|
||||
explicit AbstractBean(int version);
|
||||
|
||||
QString ToNekorayShareLink(const QString &type);
|
||||
|
||||
[[nodiscard]] virtual QString DisplayAddress();
|
||||
|
||||
[[nodiscard]] virtual QString DisplayName();
|
||||
|
||||
virtual QString DisplayType() { return {}; };
|
||||
|
||||
virtual QString DisplayTypeAndName();
|
||||
|
||||
virtual bool NeedExternal() { return false; };
|
||||
|
||||
virtual CoreObjOutboundBuildResult BuildCoreObj() { return {}; };
|
||||
|
||||
virtual ExternalBuildResult BuildExternal(int mapping_port, int socks_port) { return {}; };
|
||||
|
||||
virtual QString ToShareLink() { return {}; };
|
||||
|
||||
virtual QString InsecureHint() { return {}; };
|
||||
|
||||
};
|
||||
|
||||
QString DisplayInsecureHint(const QSharedPointer<AbstractBean> &);
|
||||
|
||||
}
|
||||
175
fmt/Bean2CoreObj.cpp
Normal file
175
fmt/Bean2CoreObj.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
#include "db/ProxyEntity.hpp"
|
||||
#include "fmt/includes.h"
|
||||
|
||||
#define MAKE_SETTINGS_STREAM_SETTINGS \
|
||||
if (!stream->packet_encoding.isEmpty()) settings["packetEncoding"] = stream->packet_encoding; \
|
||||
outbound["settings"] = settings; \
|
||||
auto streamSettings = stream->BuildStreamSettings(); \
|
||||
outbound["streamSettings"] = streamSettings;
|
||||
|
||||
namespace NekoRay::fmt {
|
||||
|
||||
QJsonObject V2rayStreamSettings::BuildStreamSettings() {
|
||||
QJsonObject streamSettings{
|
||||
{"network", network},
|
||||
{"security", security},
|
||||
};
|
||||
|
||||
if (network == "ws") {
|
||||
QJsonObject ws;
|
||||
if (!path.isEmpty()) ws["path"] = path;
|
||||
if (!host.isEmpty()) ws["headers"] = QJsonObject{{"Host", host}};
|
||||
streamSettings["wsSettings"] = ws;
|
||||
} else if (network == "h2") {
|
||||
QJsonObject h2;
|
||||
if (!path.isEmpty()) h2["path"] = path;
|
||||
if (!host.isEmpty()) h2["host"] = QList2QJsonArray(host.split(","));
|
||||
streamSettings["httpSettings"] = h2;
|
||||
} else if (network == "grpc") {
|
||||
QJsonObject grpc;
|
||||
if (!path.isEmpty()) grpc["serviceName"] = path;
|
||||
streamSettings["grpcSettings"] = grpc;
|
||||
}
|
||||
|
||||
if (security == "tls") {
|
||||
QJsonObject tls;
|
||||
if (!sni.isEmpty()) tls["serverName"] = sni;
|
||||
if (allow_insecure || dataStore->skip_cert) tls["allowInsecure"] = true;
|
||||
if (!certificate.isEmpty())
|
||||
tls["certificates"] = QJsonArray{
|
||||
QJsonObject{
|
||||
{"certificate", certificate},
|
||||
},
|
||||
};
|
||||
streamSettings["tlsSettings"] = tls;
|
||||
}
|
||||
|
||||
return streamSettings;
|
||||
}
|
||||
|
||||
CoreObjOutboundBuildResult SocksHttpBean::BuildCoreObj() {
|
||||
CoreObjOutboundBuildResult result;
|
||||
|
||||
QJsonObject outbound;
|
||||
outbound["protocol"] = socks_http_type == type_HTTP ? "http" : "socks";
|
||||
|
||||
QJsonObject settings;
|
||||
QJsonArray servers;
|
||||
QJsonObject server;
|
||||
|
||||
server["address"] = serverAddress;
|
||||
server["port"] = serverPort;
|
||||
|
||||
QJsonArray users;
|
||||
QJsonObject user;
|
||||
user["user"] = username;
|
||||
user["pass"] = password;
|
||||
users.push_back(user);
|
||||
if (!username.isEmpty() && !password.isEmpty()) server["users"] = users;
|
||||
|
||||
servers.push_back(server);
|
||||
settings["servers"] = servers;
|
||||
|
||||
MAKE_SETTINGS_STREAM_SETTINGS
|
||||
|
||||
result.outbound = outbound;
|
||||
return result;
|
||||
}
|
||||
|
||||
CoreObjOutboundBuildResult ShadowSocksBean::BuildCoreObj() {
|
||||
CoreObjOutboundBuildResult result;
|
||||
|
||||
QJsonObject outbound;
|
||||
outbound["protocol"] = "shadowsocks";
|
||||
|
||||
QJsonObject settings;
|
||||
QJsonArray servers;
|
||||
QJsonObject server;
|
||||
|
||||
server["address"] = serverAddress;
|
||||
server["port"] = serverPort;
|
||||
server["method"] = method;
|
||||
server["password"] = password;
|
||||
|
||||
servers.push_back(server);
|
||||
settings["servers"] = servers;
|
||||
|
||||
if (!plugin.isEmpty()) {
|
||||
settings["plugin"] = SubStrBefore(plugin, ";");
|
||||
settings["pluginOpts"] = SubStrAfter(plugin, ";");
|
||||
}
|
||||
|
||||
MAKE_SETTINGS_STREAM_SETTINGS
|
||||
|
||||
result.outbound = outbound;
|
||||
return result;
|
||||
}
|
||||
|
||||
CoreObjOutboundBuildResult VMessBean::BuildCoreObj() {
|
||||
CoreObjOutboundBuildResult result;
|
||||
QJsonObject outbound{
|
||||
{"protocol", "vmess"},
|
||||
};
|
||||
|
||||
QJsonObject settings{
|
||||
{"vnext", QJsonArray{
|
||||
QJsonObject{
|
||||
{"address", serverAddress},
|
||||
{"port", serverPort},
|
||||
{"users", QJsonArray{
|
||||
QJsonObject{
|
||||
{"id", uuid},
|
||||
{"alterId", aid},
|
||||
{"security", security},
|
||||
}
|
||||
}},
|
||||
}
|
||||
}}
|
||||
};
|
||||
|
||||
MAKE_SETTINGS_STREAM_SETTINGS
|
||||
|
||||
result.outbound = outbound;
|
||||
return result;
|
||||
}
|
||||
|
||||
CoreObjOutboundBuildResult TrojanVLESSBean::BuildCoreObj() {
|
||||
CoreObjOutboundBuildResult result;
|
||||
QJsonObject outbound{
|
||||
{"protocol", proxy_type == proxy_VLESS ? "vless" : "trojan"},
|
||||
};
|
||||
|
||||
QJsonObject settings;
|
||||
if (proxy_type == proxy_VLESS) {
|
||||
settings = QJsonObject{
|
||||
{"vnext", QJsonArray{
|
||||
QJsonObject{
|
||||
{"address", serverAddress},
|
||||
{"port", serverPort},
|
||||
{"users", QJsonArray{
|
||||
QJsonObject{
|
||||
{"id", password},
|
||||
{"encryption", "none"},
|
||||
}
|
||||
}},
|
||||
}
|
||||
}}
|
||||
};
|
||||
} else {
|
||||
settings = QJsonObject{
|
||||
{"servers", QJsonArray{
|
||||
QJsonObject{
|
||||
{"address", serverAddress},
|
||||
{"port", serverPort},
|
||||
{"password", password},
|
||||
}
|
||||
}}
|
||||
};
|
||||
}
|
||||
|
||||
MAKE_SETTINGS_STREAM_SETTINGS
|
||||
|
||||
result.outbound = outbound;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
81
fmt/Bean2External.cpp
Normal file
81
fmt/Bean2External.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#include "db/ProxyEntity.hpp"
|
||||
#include "fmt/includes.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
|
||||
#define WriteTempFile(fn, data) \
|
||||
QDir dir; \
|
||||
if (!dir.exists("temp")) dir.mkdir("temp"); \
|
||||
QFile f(QString("temp/") + fn); \
|
||||
bool ok = f.open(QIODevice::WriteOnly | QIODevice::Truncate); \
|
||||
if (ok) { \
|
||||
f.write(data); \
|
||||
} else { \
|
||||
result.error = f.errorString(); \
|
||||
} \
|
||||
f.close(); \
|
||||
auto TempFile = QFileInfo(f).absoluteFilePath();
|
||||
|
||||
namespace NekoRay::fmt {
|
||||
ExternalBuildResult NaiveBean::BuildExternal(int mapping_port, int socks_port) {
|
||||
ExternalBuildResult result{dataStore->extraCore->Get("naive")};
|
||||
if (result.program.isEmpty()) {
|
||||
result.error = QObject::tr("Core not found: %1").arg(DisplayType());
|
||||
return result;
|
||||
}
|
||||
|
||||
auto _serverAddress = sni.isEmpty() ? serverAddress : sni;
|
||||
|
||||
result.arguments += "--log";
|
||||
result.arguments += "--listen=socks://127.0.0.1:" + Int2String(socks_port);
|
||||
result.arguments += "--proxy=" + protocol + "://" +
|
||||
username + ":" + password + "@" +
|
||||
_serverAddress + ":" + Int2String(mapping_port);
|
||||
result.arguments += "--host-resolver-rules=MAP " + _serverAddress + " 127.0.0.1";
|
||||
if (insecure_concurrency > 0) result.arguments += "--insecure-concurrency=" + Int2String(insecure_concurrency);
|
||||
if (!extra_headers.isEmpty()) result.arguments += "--extra-headers=" + extra_headers;
|
||||
if (!certificate.isEmpty()) {
|
||||
WriteTempFile("naive_" + GetRandomString(10) + ".crt", certificate.toUtf8());
|
||||
result.env += "SSL_CERT_FILE=" + TempFile;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ExternalBuildResult CustomBean::BuildExternal(int mapping_port, int socks_port) {
|
||||
ExternalBuildResult result{dataStore->extraCore->Get(core)};
|
||||
if (result.program.isEmpty()) {
|
||||
result.error = QObject::tr("Core not found: %1").arg(DisplayType());
|
||||
return result;
|
||||
}
|
||||
|
||||
result.arguments = command; // TODO split?
|
||||
|
||||
for (int i = 0; i < result.arguments.length(); i++) {
|
||||
auto arg = result.arguments[i];
|
||||
if (arg.contains("%mapping_port%")) {
|
||||
arg = arg.replace("%mapping_port%", Int2String(mapping_port));
|
||||
} else if (arg.contains("%socks_port%")) {
|
||||
arg = arg.replace("%socks_port%", Int2String(socks_port));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
result.arguments[i] = arg;
|
||||
}
|
||||
|
||||
if (!config_simple.trimmed().isEmpty()) {
|
||||
auto config = config_simple;
|
||||
config = config.replace("%mapping_port%", Int2String(mapping_port));
|
||||
config = config.replace("%socks_port%", Int2String(socks_port));
|
||||
|
||||
WriteTempFile("custom_cfg_" + GetRandomString(10) + ".tmp", config.toUtf8());
|
||||
for (int i = 0; i < result.arguments.count(); i++) {
|
||||
result.arguments[i] = result.arguments[i].replace("%config%", TempFile);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
94
fmt/Bean2Link.cpp
Normal file
94
fmt/Bean2Link.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#include "db/ProxyEntity.hpp"
|
||||
#include "fmt/includes.h"
|
||||
|
||||
#include <QUrlQuery>
|
||||
|
||||
namespace NekoRay::fmt {
|
||||
QString SocksHttpBean::ToShareLink() {
|
||||
QUrl url;
|
||||
if (socks_http_type == type_HTTP) { // http
|
||||
if (stream->security == "tls") {
|
||||
url.setScheme("https");
|
||||
} else {
|
||||
url.setScheme("http");
|
||||
}
|
||||
} else {
|
||||
url.setScheme(QString("socks%1").arg(socks_http_type));
|
||||
}
|
||||
if (!name.isEmpty()) url.setFragment(UrlSafe_encode(name));
|
||||
if (!username.isEmpty()) url.setUserName(username);
|
||||
if (!password.isEmpty()) url.setPassword(password);
|
||||
url.setHost(serverAddress);
|
||||
url.setPort(serverPort);
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
QString TrojanVLESSBean::ToShareLink() {
|
||||
QUrl url;
|
||||
QUrlQuery query;
|
||||
url.setScheme(proxy_type == proxy_VLESS ? "vless" : "trojan");
|
||||
url.setUserName(password);
|
||||
url.setHost(serverAddress);
|
||||
url.setPort(serverPort);
|
||||
if (!name.isEmpty()) url.setFragment(UrlSafe_encode(name));
|
||||
if (!stream->sni.isEmpty()) query.addQueryItem("sni", stream->sni);
|
||||
query.addQueryItem("security", "tls");
|
||||
query.addQueryItem("type", stream->network.replace("h2", "http"));
|
||||
|
||||
if (stream->network == "ws" || stream->network == "h2") {
|
||||
if (!stream->path.isEmpty()) query.addQueryItem("path", stream->path);
|
||||
if (!stream->host.isEmpty()) query.addQueryItem("host", stream->host);
|
||||
} else if (stream->network == "grpc") {
|
||||
if (!stream->path.isEmpty()) query.addQueryItem("serviceName", stream->path);
|
||||
}
|
||||
|
||||
url.setQuery(query);
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
QString ShadowSocksBean::ToShareLink() {
|
||||
QUrl url;
|
||||
url.setScheme("ss");
|
||||
auto username = method + ":" + password;
|
||||
url.setUserName(username.toUtf8().toBase64(QByteArray::Base64Option::Base64UrlEncoding));
|
||||
url.setHost(serverAddress);
|
||||
url.setPort(serverPort);
|
||||
if (!name.isEmpty()) url.setFragment(UrlSafe_encode(name));
|
||||
QUrlQuery q;
|
||||
if (!plugin.isEmpty()) q.addQueryItem("plugin", plugin);
|
||||
if (!q.isEmpty()) url.setQuery(q);
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
QString VMessBean::ToShareLink() {
|
||||
QJsonObject N{
|
||||
{"v", 2},
|
||||
{"ps", name},
|
||||
{"add", serverAddress},
|
||||
{"port", serverPort},
|
||||
{"id", uuid},
|
||||
{"aid", aid},
|
||||
{"net", stream->network},
|
||||
{"host", stream->host},
|
||||
{"path", stream->path},
|
||||
{"type", stream->header_type},
|
||||
{"scy", security},
|
||||
// TODO header type
|
||||
{"tls", stream->security == "tls" ? "tls" : ""},
|
||||
{"sni", stream->sni},
|
||||
};
|
||||
return "vmess://" + QJsonObject2QString(N, false).toUtf8().toBase64();
|
||||
}
|
||||
|
||||
QString NaiveBean::ToShareLink() {
|
||||
QUrl url;
|
||||
url.setScheme("https+naive");
|
||||
url.setUserName(username);
|
||||
url.setPassword(password);
|
||||
url.setHost(serverAddress);
|
||||
url.setPort(serverPort);
|
||||
if (!name.isEmpty()) url.setFragment(UrlSafe_encode(name));
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
}
|
||||
18
fmt/ChainBean.hpp
Normal file
18
fmt/ChainBean.hpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "main/NekoRay.hpp"
|
||||
|
||||
namespace NekoRay::fmt {
|
||||
class ChainBean : public AbstractBean {
|
||||
public:
|
||||
QList<int> list; // in to out
|
||||
|
||||
ChainBean() : AbstractBean(0) {
|
||||
_add(new configItem("list", &list, itemType::integerList));
|
||||
};
|
||||
|
||||
QString DisplayType() override { return QObject::tr("Chain Proxy"); };
|
||||
|
||||
QString DisplayAddress() override { return ""; };
|
||||
};
|
||||
}
|
||||
26
fmt/CustomBean.hpp
Normal file
26
fmt/CustomBean.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "fmt/AbstractBean.hpp"
|
||||
|
||||
namespace NekoRay::fmt {
|
||||
class CustomBean : public AbstractBean {
|
||||
public:
|
||||
QString core;
|
||||
QList<QString> command;
|
||||
// QString config_map; // map: fn to text
|
||||
QString config_simple;
|
||||
|
||||
CustomBean() : AbstractBean(0) {
|
||||
_add(new configItem("core", &core, itemType::string));
|
||||
_add(new configItem("cmd", &command, itemType::stringList));
|
||||
// _add(new configItem("cm", &config_map, itemType::string));
|
||||
_add(new configItem("cs", &config_simple, itemType::string));
|
||||
};
|
||||
|
||||
QString DisplayType() override { return core; };
|
||||
|
||||
bool NeedExternal() override { return true; };
|
||||
|
||||
ExternalBuildResult BuildExternal(int mapping_port, int socks_port) override;
|
||||
};
|
||||
}
|
||||
67
fmt/InsecureHint.cpp
Normal file
67
fmt/InsecureHint.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "V2RayStreamSettings.hpp"
|
||||
#include "ShadowSocksBean.hpp"
|
||||
#include "VMessBean.hpp"
|
||||
#include "TrojanVLESSBean.hpp"
|
||||
#include "SocksHttpBean.hpp"
|
||||
|
||||
namespace NekoRay::fmt {
|
||||
QString DisplayInsecureHint(const QSharedPointer<AbstractBean> &bean) {
|
||||
if (!dataStore->insecure_hint) return {};
|
||||
auto insecure_hint = bean->InsecureHint();
|
||||
auto stream = GetStreamSettings(bean);
|
||||
if (stream != nullptr) insecure_hint += "\n" + stream->InsecureHint();
|
||||
return insecure_hint.trimmed();
|
||||
}
|
||||
|
||||
QString V2rayStreamSettings::InsecureHint() const {
|
||||
if (allow_insecure) {
|
||||
return QObject::tr(
|
||||
"The configuration (insecure) can be detected and identified, the transmission is fully visible to the censor and is not resistant to man-in-the-middle tampering with the content of the communication."
|
||||
);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QString ShadowSocksBean::InsecureHint() {
|
||||
if (method.contains("-poly") || method.contains("-gcm")) {
|
||||
return {};
|
||||
}
|
||||
return QObject::tr(
|
||||
"This configuration (Shadowsocks streaming cipher) can be accurately proactively detected and decrypted by censors without requiring a password, and cannot be mitigated by turning on IV replay filters on the server side.\n"
|
||||
"\n"
|
||||
"Learn more: https://github.com/net4people/bbs/issues/24"
|
||||
);
|
||||
}
|
||||
|
||||
QString VMessBean::InsecureHint() {
|
||||
if (security == "none" || security == "zero") {
|
||||
if (stream->security.isEmpty() || stream->security == "none") {
|
||||
return QObject::tr(
|
||||
"This profile is cleartext, don't use it if the server is not in your local network.");
|
||||
}
|
||||
}
|
||||
if (aid > 0) {
|
||||
return QObject::tr(
|
||||
"This configuration (VMess MD5 authentication) has been deprecated by upstream because of its questionable resistance to tampering and concealment.\n"
|
||||
"\n"
|
||||
"As of January 1, 2022, compatibility with MD5 authentication information will be disabled on the server side by default. Any client using MD5 authentication information will not be able to connect to a server with VMess MD5 authentication information disabled."
|
||||
);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QString TrojanVLESSBean::InsecureHint() {
|
||||
if (stream->security.isEmpty() || stream->security == "none") {
|
||||
return QObject::tr("This profile is cleartext, don't use it if the server is not in your local network.");
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QString SocksHttpBean::InsecureHint() {
|
||||
if (stream->security.isEmpty() || stream->security == "none") {
|
||||
return QObject::tr("This profile is cleartext, don't use it if the server is not in your local network.");
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
147
fmt/Link2Bean.cpp
Normal file
147
fmt/Link2Bean.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
#include "db/ProxyEntity.hpp"
|
||||
#include "fmt/includes.h"
|
||||
|
||||
#include <QUrlQuery>
|
||||
|
||||
namespace NekoRay::fmt {
|
||||
|
||||
#define DECODE_V2RAY_N_1 auto linkN = DecodeB64IfValid(SubStrBefore(SubStrAfter(link, "://"), "#"), QByteArray::Base64Option::Base64UrlEncoding); \
|
||||
if (linkN.isEmpty()) return false; \
|
||||
auto hasRemarks = link.contains("#"); \
|
||||
if (hasRemarks) linkN += "#" + SubStrAfter(link, "#"); \
|
||||
auto url = QUrl("https://" + linkN);
|
||||
|
||||
bool SocksHttpBean::TryParseLink(const QString &link) {
|
||||
if (!SubStrAfter(link, "://").contains(":")) {
|
||||
// v2rayN shit format
|
||||
DECODE_V2RAY_N_1
|
||||
|
||||
if (hasRemarks) name = url.fragment(QUrl::FullyDecoded);
|
||||
serverAddress = url.host();
|
||||
serverPort = url.port();
|
||||
username = url.userName();
|
||||
password = url.password();
|
||||
} else {
|
||||
auto url = QUrl(link);
|
||||
if (!url.isValid()) return false;
|
||||
auto query = GetQuery(url);
|
||||
|
||||
if (link.startsWith("socks4")) socks_http_type = type_Socks4;
|
||||
if (link.startsWith("http")) socks_http_type = type_HTTP;
|
||||
serverAddress = url.host();
|
||||
serverPort = url.port();
|
||||
username = url.userName();
|
||||
password = url.password();
|
||||
if (serverPort == -1) serverPort = socks_http_type == type_HTTP ? 443 : 1080;
|
||||
|
||||
stream->security = GetQueryValue(query, "security", "") == "true" ? "tls" : "none";
|
||||
stream->sni = GetQueryValue(query, "sni");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TrojanVLESSBean::TryParseLink(const QString &link) {
|
||||
auto url = QUrl(link);
|
||||
if (!url.isValid()) return false;
|
||||
auto query = GetQuery(url);
|
||||
|
||||
name = url.fragment(QUrl::FullyDecoded);
|
||||
serverAddress = url.host();
|
||||
serverPort = url.port();
|
||||
password = url.userName();
|
||||
if (serverPort == -1) serverPort = 443;
|
||||
|
||||
stream->network = GetQueryValue(query, "type", "tcp").replace("http", "h2");
|
||||
stream->security = GetQueryValue(query, "security", "tls");
|
||||
auto sni1 = GetQueryValue(query, "sni");
|
||||
auto sni2 = GetQueryValue(query, "peer");
|
||||
if (!sni1.isEmpty()) stream->sni = sni1;
|
||||
if (!sni2.isEmpty()) stream->sni = sni2;
|
||||
if (!query.queryItemValue("allowInsecure").isEmpty()) stream->allow_insecure = true;
|
||||
|
||||
// TODO header kcp quic
|
||||
if (stream->network == "ws") {
|
||||
stream->path = GetQueryValue(query, "path", "");
|
||||
stream->host = GetQueryValue(query, "host", "");
|
||||
} else if (stream->network == "h2") {
|
||||
stream->path = GetQueryValue(query, "path", "");
|
||||
stream->host = GetQueryValue(query, "host", "").replace("|", ",");
|
||||
} else if (stream->network == "grpc") {
|
||||
stream->path = GetQueryValue(query, "serviceName", "");
|
||||
}
|
||||
|
||||
return !password.isEmpty();
|
||||
}
|
||||
|
||||
bool ShadowSocksBean::TryParseLink(const QString &link) {
|
||||
if (SubStrBefore(link, "#").contains("@")) {
|
||||
// SS
|
||||
auto url = QUrl(link);
|
||||
if (!url.isValid()) return false;
|
||||
|
||||
name = url.fragment(QUrl::FullyDecoded);
|
||||
serverAddress = url.host();
|
||||
serverPort = url.port();
|
||||
auto method_password = DecodeB64IfValid(url.userName(), QByteArray::Base64Option::Base64UrlEncoding);
|
||||
if (method_password.isEmpty()) return false;
|
||||
method = SubStrBefore(method_password, ":");
|
||||
password = SubStrAfter(method_password, ":");
|
||||
auto query = GetQuery(url);
|
||||
plugin = query.queryItemValue("plugin").replace("simple-obfs;", "obfs-local;");
|
||||
} else {
|
||||
// v2rayN
|
||||
DECODE_V2RAY_N_1
|
||||
|
||||
if (hasRemarks) name = url.fragment(QUrl::FullyDecoded);
|
||||
serverAddress = url.host();
|
||||
serverPort = url.port();
|
||||
method = url.userName();
|
||||
password = url.password();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VMessBean::TryParseLink(const QString &link) {
|
||||
// V2RayN Format
|
||||
auto linkN = DecodeB64IfValid(SubStrAfter(link, "vmess://"));
|
||||
if (!linkN.isEmpty()) {
|
||||
auto objN = QString2QJsonObject(linkN);
|
||||
if (objN.isEmpty()) return false;
|
||||
// REQUIRED
|
||||
uuid = objN["id"].toString();
|
||||
serverAddress = objN["add"].toString();
|
||||
serverPort = objN["port"].toVariant().toInt();
|
||||
// OPTIONAL
|
||||
name = objN["ps"].toString();
|
||||
aid = objN["aid"].toInt();
|
||||
stream->host = objN["host"].toString();
|
||||
stream->path = objN["path"].toString();
|
||||
stream->sni = objN["sni"].toString();
|
||||
auto net = objN["net"].toString().replace("http", "h2");
|
||||
if (!net.isEmpty()) stream->network = net;
|
||||
auto scy = objN["scy"].toString();
|
||||
if (!scy.isEmpty()) security = scy;
|
||||
// TLS (XTLS?)
|
||||
if (!objN["tls"].toString().isEmpty()) stream->security = "tls";
|
||||
// TODO quic & kcp
|
||||
return true;
|
||||
}
|
||||
|
||||
// Std Format
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NaiveBean::TryParseLink(const QString &link) {
|
||||
auto url = QUrl(link);
|
||||
if (!url.isValid()) return false;
|
||||
|
||||
name = url.fragment(QUrl::FullyDecoded);
|
||||
serverAddress = url.host();
|
||||
serverPort = url.port();
|
||||
username = url.userName();
|
||||
password = url.password();
|
||||
|
||||
return !(username.isEmpty() || password.isEmpty());
|
||||
}
|
||||
|
||||
}
|
||||
36
fmt/NaiveBean.hpp
Normal file
36
fmt/NaiveBean.hpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "fmt/AbstractBean.hpp"
|
||||
|
||||
namespace NekoRay::fmt {
|
||||
class NaiveBean : public AbstractBean {
|
||||
public:
|
||||
QString username = "";
|
||||
QString password = "";
|
||||
QString protocol = "https";
|
||||
QString extra_headers = "";
|
||||
QString sni = "";
|
||||
QString certificate = "";
|
||||
int insecure_concurrency = 0;
|
||||
|
||||
NaiveBean() : AbstractBean(0) {
|
||||
_add(new configItem("username", &username, itemType::string));
|
||||
_add(new configItem("password", &password, itemType::string));
|
||||
_add(new configItem("protocol", &protocol, itemType::string));
|
||||
_add(new configItem("extra_headers", &extra_headers, itemType::string));
|
||||
_add(new configItem("sni", &sni, itemType::string));
|
||||
_add(new configItem("certificate", &certificate, itemType::string));
|
||||
_add(new configItem("insecure_concurrency", &insecure_concurrency, itemType::integer));
|
||||
};
|
||||
|
||||
QString DisplayType() override { return "Naive"; };
|
||||
|
||||
bool NeedExternal() override { return true; };
|
||||
|
||||
ExternalBuildResult BuildExternal(int mapping_port, int socks_port) override;
|
||||
|
||||
bool TryParseLink(const QString &link);
|
||||
|
||||
QString ToShareLink() override;
|
||||
};
|
||||
}
|
||||
34
fmt/ShadowSocksBean.hpp
Normal file
34
fmt/ShadowSocksBean.hpp
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "fmt/AbstractBean.hpp"
|
||||
#include "fmt/V2RayStreamSettings.hpp"
|
||||
|
||||
namespace NekoRay::fmt {
|
||||
class ShadowSocksBean : public AbstractBean {
|
||||
public:
|
||||
QString method = "aes-128-gcm";
|
||||
QString password = "";
|
||||
QString plugin = "";
|
||||
|
||||
QSharedPointer<V2rayStreamSettings> stream = QSharedPointer<V2rayStreamSettings>(new V2rayStreamSettings());
|
||||
QString custom = "";
|
||||
|
||||
ShadowSocksBean() : AbstractBean(0) {
|
||||
_add(new configItem("method", &method, itemType::string));
|
||||
_add(new configItem("pass", &password, itemType::string));
|
||||
_add(new configItem("plugin", &plugin, itemType::string));
|
||||
_add(new configItem("stream", dynamic_cast<JsonStore *>(stream.get()), itemType::jsonStore));
|
||||
_add(new configItem("custom", &custom, itemType::string));
|
||||
};
|
||||
|
||||
QString DisplayType() override { return "Shadowsocks"; };
|
||||
|
||||
CoreObjOutboundBuildResult BuildCoreObj() override;
|
||||
|
||||
bool TryParseLink(const QString &link);
|
||||
|
||||
QString ToShareLink() override;
|
||||
|
||||
QString InsecureHint() override;
|
||||
};
|
||||
}
|
||||
39
fmt/SocksHttpBean.hpp
Normal file
39
fmt/SocksHttpBean.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "fmt/AbstractBean.hpp"
|
||||
#include "fmt/V2RayStreamSettings.hpp"
|
||||
|
||||
namespace NekoRay::fmt {
|
||||
class SocksHttpBean : public AbstractBean {
|
||||
public:
|
||||
static constexpr int type_HTTP = -80;
|
||||
static constexpr int type_Socks4 = 4;
|
||||
static constexpr int type_Socks5 = 5;
|
||||
|
||||
int socks_http_type = type_Socks5;
|
||||
QString username = "";
|
||||
QString password = "";
|
||||
|
||||
QSharedPointer<V2rayStreamSettings> stream = QSharedPointer<V2rayStreamSettings>(new V2rayStreamSettings());
|
||||
QString custom = "";
|
||||
|
||||
explicit SocksHttpBean(int _socks_http_type) : AbstractBean(0) {
|
||||
this->socks_http_type = _socks_http_type;
|
||||
_add(new configItem("v", &socks_http_type, itemType::integer));
|
||||
_add(new configItem("username", &username, itemType::string));
|
||||
_add(new configItem("password", &password, itemType::string));
|
||||
_add(new configItem("stream", dynamic_cast<JsonStore *>(stream.get()), itemType::jsonStore));
|
||||
_add(new configItem("custom", &custom, itemType::string));
|
||||
};
|
||||
|
||||
QString DisplayType() override { return socks_http_type == type_HTTP ? "HTTP" : "Socks"; };
|
||||
|
||||
CoreObjOutboundBuildResult BuildCoreObj() override;
|
||||
|
||||
bool TryParseLink(const QString &link);
|
||||
|
||||
QString ToShareLink() override;
|
||||
|
||||
QString InsecureHint() override;
|
||||
};
|
||||
}
|
||||
35
fmt/TrojanVLESSBean.hpp
Normal file
35
fmt/TrojanVLESSBean.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "fmt/AbstractBean.hpp"
|
||||
#include "fmt/V2RayStreamSettings.hpp"
|
||||
|
||||
namespace NekoRay::fmt {
|
||||
class TrojanVLESSBean : public AbstractBean {
|
||||
public:
|
||||
static constexpr int proxy_Trojan = 0;
|
||||
static constexpr int proxy_VLESS = 1;
|
||||
int proxy_type = proxy_Trojan;
|
||||
|
||||
QString password = "";
|
||||
|
||||
QSharedPointer<V2rayStreamSettings> stream = QSharedPointer<V2rayStreamSettings>(new V2rayStreamSettings());
|
||||
QString custom = "";
|
||||
|
||||
explicit TrojanVLESSBean(int _proxy_type) : AbstractBean(0) {
|
||||
proxy_type = _proxy_type;
|
||||
_add(new configItem("pass", &password, itemType::string));
|
||||
_add(new configItem("stream", dynamic_cast<JsonStore *>(stream.get()), itemType::jsonStore));
|
||||
_add(new configItem("custom", &custom, itemType::string));
|
||||
};
|
||||
|
||||
QString DisplayType() override { return proxy_type == proxy_VLESS ? "VLESS" : "Trojan"; };
|
||||
|
||||
CoreObjOutboundBuildResult BuildCoreObj() override;
|
||||
|
||||
bool TryParseLink(const QString &link);
|
||||
|
||||
QString ToShareLink() override;
|
||||
|
||||
QString InsecureHint() override;
|
||||
};
|
||||
}
|
||||
53
fmt/V2RayStreamSettings.hpp
Normal file
53
fmt/V2RayStreamSettings.hpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include "AbstractBean.hpp"
|
||||
|
||||
namespace NekoRay::fmt {
|
||||
class V2rayStreamSettings : public JsonStore {
|
||||
public:
|
||||
QString network = "tcp";
|
||||
QString security = "none";
|
||||
QString packet_encoding = "";
|
||||
// ws/h2/grpc
|
||||
QString path = "";
|
||||
QString host = "";
|
||||
// ws
|
||||
int max_early_data = 0;
|
||||
QString early_data_header_name = "";
|
||||
// QUIC & KCP
|
||||
QString header_type = "";
|
||||
// tls
|
||||
QString sni = "";
|
||||
QString certificate = "";
|
||||
bool allow_insecure = false;
|
||||
|
||||
V2rayStreamSettings() : JsonStore() {
|
||||
_add(new configItem("net", &network, itemType::string));
|
||||
_add(new configItem("sec", &security, itemType::string));
|
||||
_add(new configItem("pac_enc", &packet_encoding, itemType::string));
|
||||
_add(new configItem("path", &path, itemType::string));
|
||||
_add(new configItem("host", &host, itemType::string));
|
||||
_add(new configItem("sni", &sni, itemType::string));
|
||||
_add(new configItem("cert", &certificate, itemType::string));
|
||||
_add(new configItem("insecure", &allow_insecure, itemType::boolean));
|
||||
_add(new configItem("ws_med", &max_early_data, itemType::integer));
|
||||
_add(new configItem("ws_edhn", &early_data_header_name, itemType::string));
|
||||
_add(new configItem("h_type", &header_type, itemType::string));
|
||||
}
|
||||
|
||||
QJsonObject BuildStreamSettings();
|
||||
|
||||
[[nodiscard]] QString InsecureHint() const;
|
||||
};
|
||||
|
||||
inline V2rayStreamSettings *GetStreamSettings(const QSharedPointer<AbstractBean> &bean) {
|
||||
if (bean == nullptr) return nullptr;
|
||||
auto stream_item = bean->_get("stream");
|
||||
if (stream_item != nullptr) {
|
||||
auto stream_store = (NekoRay::JsonStore *) stream_item->ptr;
|
||||
auto stream = (NekoRay::fmt::V2rayStreamSettings *) stream_store;
|
||||
return stream;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
35
fmt/VMessBean.hpp
Normal file
35
fmt/VMessBean.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "fmt/AbstractBean.hpp"
|
||||
#include "fmt/V2RayStreamSettings.hpp"
|
||||
|
||||
namespace NekoRay::fmt {
|
||||
class VMessBean : public AbstractBean {
|
||||
public:
|
||||
QString uuid = "";
|
||||
int aid = 0;
|
||||
QString security = "auto";
|
||||
|
||||
QSharedPointer<V2rayStreamSettings> stream = QSharedPointer<V2rayStreamSettings>(new V2rayStreamSettings());
|
||||
QString custom = "";
|
||||
|
||||
VMessBean() : AbstractBean(0) {
|
||||
_add(new configItem("id", &uuid, itemType::string));
|
||||
_add(new configItem("aid", &aid, itemType::integer));
|
||||
_add(new configItem("sec", &security, itemType::string));
|
||||
_add(new configItem("stream", dynamic_cast<JsonStore *>(stream.get()), itemType::jsonStore));
|
||||
_add(new configItem("custom", &custom, itemType::string));
|
||||
};
|
||||
|
||||
QString DisplayType() override { return "VMess"; };
|
||||
|
||||
CoreObjOutboundBuildResult BuildCoreObj() override;
|
||||
|
||||
bool TryParseLink(const QString &link);
|
||||
|
||||
QString ToShareLink() override;
|
||||
|
||||
QString InsecureHint() override;
|
||||
};
|
||||
}
|
||||
|
||||
9
fmt/includes.h
Normal file
9
fmt/includes.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "SocksHttpBean.hpp"
|
||||
#include "ShadowSocksBean.hpp"
|
||||
#include "ChainBean.hpp"
|
||||
#include "VMessBean.hpp"
|
||||
#include "TrojanVLESSBean.hpp"
|
||||
#include "NaiveBean.hpp"
|
||||
#include "CustomBean.hpp"
|
||||
6
go/.gitignore
vendored
Normal file
6
go/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
*.log
|
||||
*.pem
|
||||
*.json
|
||||
*.exe
|
||||
*.dat
|
||||
/nekoray_core
|
||||
54
go/auth.go
Normal file
54
go/auth.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// Authenticator exposes a function for authenticating requests.
|
||||
type Authenticator struct {
|
||||
Token string
|
||||
}
|
||||
|
||||
// Authenticate checks that a token exists and is valid. It stores the user
|
||||
// metadata in the returned context and removes the token from the context.
|
||||
func (a Authenticator) Authenticate(ctx context.Context) (newCtx context.Context, err error) {
|
||||
auth, err := extractHeader(ctx, "nekoray_auth")
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
if auth != a.Token {
|
||||
return ctx, status.Error(codes.Unauthenticated, "invalid token")
|
||||
}
|
||||
|
||||
return purgeHeader(ctx, "nekoray_auth"), nil
|
||||
}
|
||||
|
||||
func extractHeader(ctx context.Context, header string) (string, error) {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return "", status.Error(codes.Unauthenticated, "no headers in request")
|
||||
}
|
||||
|
||||
authHeaders, ok := md[header]
|
||||
if !ok {
|
||||
return "", status.Error(codes.Unauthenticated, "no header in request")
|
||||
}
|
||||
|
||||
if len(authHeaders) != 1 {
|
||||
return "", status.Error(codes.Unauthenticated, "more than 1 header in request")
|
||||
}
|
||||
|
||||
return authHeaders[0], nil
|
||||
}
|
||||
|
||||
func purgeHeader(ctx context.Context, header string) context.Context {
|
||||
md, _ := metadata.FromIncomingContext(ctx)
|
||||
mdCopy := md.Copy()
|
||||
mdCopy[header] = nil
|
||||
return metadata.NewIncomingContext(ctx, mdCopy)
|
||||
}
|
||||
323
go/core_rpc.go
Normal file
323
go/core_rpc.go
Normal file
@@ -0,0 +1,323 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"libcore"
|
||||
"libcore/device"
|
||||
"libcore/stun"
|
||||
"nekoray_core/gen"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var instance *libcore.V2RayInstance
|
||||
|
||||
func setupCore() {
|
||||
device.IsNekoray = true
|
||||
libcore.SetConfig("", false, true)
|
||||
libcore.InitCore("", "", "", nil, ".", "moe.nekoray.pc:bg", true, 50)
|
||||
}
|
||||
|
||||
func (s *server) Start(ctx context.Context, in *gen.LoadConfigReq) (out *gen.ErrorResp, _ error) {
|
||||
var err error
|
||||
|
||||
// only error use this
|
||||
defer func() {
|
||||
out = &gen.ErrorResp{}
|
||||
if err != nil {
|
||||
out.Error = err.Error()
|
||||
instance = nil
|
||||
}
|
||||
}()
|
||||
|
||||
if nekoray_debug {
|
||||
logrus.Println("Start:", in)
|
||||
}
|
||||
|
||||
if instance != nil {
|
||||
err = errors.New("instance already started")
|
||||
return
|
||||
}
|
||||
|
||||
instance = libcore.NewV2rayInstance()
|
||||
|
||||
libcore.SetConfig(in.TryDomains, false, true)
|
||||
|
||||
err = instance.LoadConfig(in.CoreConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = instance.Start()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
TunSetV2ray(instance)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *server) SetTun(ctx context.Context, in *gen.SetTunReq) (out *gen.ErrorResp, _ error) {
|
||||
var err error
|
||||
|
||||
// only error use this
|
||||
defer func() {
|
||||
out = &gen.ErrorResp{}
|
||||
if err != nil {
|
||||
out.Error = err.Error()
|
||||
}
|
||||
}()
|
||||
|
||||
if in.Implementation >= 0 { //Start
|
||||
err = TunStart(in)
|
||||
} else { //Stop
|
||||
TunStop()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *server) Stop(ctx context.Context, in *gen.EmptyReq) (out *gen.ErrorResp, _ error) {
|
||||
var err error
|
||||
|
||||
// only error use this
|
||||
defer func() {
|
||||
out = &gen.ErrorResp{}
|
||||
if err != nil {
|
||||
out.Error = err.Error()
|
||||
}
|
||||
}()
|
||||
|
||||
if instance == nil {
|
||||
return
|
||||
}
|
||||
|
||||
TunSetV2ray(nil)
|
||||
|
||||
err = instance.Close()
|
||||
instance = nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *server) Exit(ctx context.Context, in *gen.EmptyReq) (out *gen.EmptyResp, _ error) {
|
||||
out = &gen.EmptyResp{}
|
||||
|
||||
// Connection closed
|
||||
os.Exit(0)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *server) Test(ctx context.Context, in *gen.TestReq) (out *gen.TestResp, _ error) {
|
||||
var err error
|
||||
out = &gen.TestResp{Ms: 0}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
out.Error = err.Error()
|
||||
}
|
||||
}()
|
||||
|
||||
if nekoray_debug {
|
||||
logrus.Println("Test:", in)
|
||||
}
|
||||
|
||||
if in.Mode == gen.TestMode_UrlTest {
|
||||
var i *libcore.V2RayInstance
|
||||
|
||||
if in.Config != nil {
|
||||
// Test instance
|
||||
i = libcore.NewV2rayInstance()
|
||||
defer i.Close()
|
||||
|
||||
err = i.LoadConfig(in.Config.CoreConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = i.Start()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Test running instance
|
||||
i = instance
|
||||
if i == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Latency
|
||||
var t int32
|
||||
t, err = libcore.UrlTestV2ray(i, in.Inbound, in.Url, in.Timeout)
|
||||
out.Ms = t // sn: ms==0 是错误
|
||||
} else if in.Mode == gen.TestMode_TcpPing {
|
||||
startTime := time.Now()
|
||||
_, err = net.DialTimeout("tcp", in.Address, time.Duration(in.Timeout)*time.Millisecond)
|
||||
endTime := time.Now()
|
||||
if err == nil {
|
||||
out.Ms = int32(endTime.Sub(startTime).Milliseconds())
|
||||
}
|
||||
} else if in.Mode == gen.TestMode_FullTest {
|
||||
if in.Config == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Test instance
|
||||
i := libcore.NewV2rayInstance()
|
||||
defer i.Close()
|
||||
|
||||
err = i.LoadConfig(in.Config.CoreConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = i.Start()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Latency
|
||||
var latency string
|
||||
if in.FullLatency {
|
||||
t, _ := libcore.UrlTestV2ray(i, in.Inbound, in.Url, in.Timeout)
|
||||
out.Ms = t
|
||||
if t > 0 {
|
||||
latency = fmt.Sprint(t, "ms")
|
||||
} else {
|
||||
latency = "Error"
|
||||
}
|
||||
}
|
||||
|
||||
// 入口 IP
|
||||
var in_ip string
|
||||
if in.FullInOut {
|
||||
_in_ip, err := net.ResolveIPAddr("ip", in.InAddress)
|
||||
if err == nil {
|
||||
in_ip = _in_ip.String()
|
||||
} else {
|
||||
in_ip = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
client := getProxyHttpClient(i)
|
||||
|
||||
// 出口 IP
|
||||
var out_ip string
|
||||
if in.FullInOut {
|
||||
resp, err := client.Get("https://httpbin.org/get")
|
||||
if err == nil {
|
||||
v := make(map[string]interface{})
|
||||
json.NewDecoder(resp.Body).Decode(&v)
|
||||
if a, ok := v["origin"]; ok {
|
||||
if s, ok := a.(string); ok {
|
||||
out_ip = s
|
||||
}
|
||||
}
|
||||
resp.Body.Close()
|
||||
} else {
|
||||
out_ip = "Error"
|
||||
}
|
||||
}
|
||||
|
||||
// 下载
|
||||
var speed string
|
||||
if in.FullSpeed {
|
||||
resp, err := client.Get("http://cachefly.cachefly.net/10mb.test")
|
||||
if err == nil {
|
||||
time_start := time.Now()
|
||||
n, _ := io.Copy(io.Discard, resp.Body)
|
||||
time_end := time.Now()
|
||||
|
||||
speed = fmt.Sprintf("%.2fMiB/s", (float64(n)/time_end.Sub(time_start).Seconds())/1048576)
|
||||
resp.Body.Close()
|
||||
} else {
|
||||
speed = "Error"
|
||||
}
|
||||
}
|
||||
|
||||
// STUN
|
||||
var stunText string
|
||||
if in.FullNat {
|
||||
timeout := time.NewTimer(time.Second * 5)
|
||||
result := make(chan string, 0)
|
||||
|
||||
go func() {
|
||||
stunServer := "206.53.159.130:3478"
|
||||
stunAddr, _ := net.ResolveUDPAddr("udp4", stunServer)
|
||||
pc, err := i.DialUDP(stunAddr)
|
||||
if err == nil {
|
||||
stunClient := stun.NewClientWithConnection(pc)
|
||||
stunClient.SetServerAddr(stunServer)
|
||||
nat, host, err, fake := stunClient.Discover()
|
||||
if err == nil {
|
||||
if host != nil {
|
||||
if fake {
|
||||
result <- fmt.Sprint("No Endpoint", nat)
|
||||
} else {
|
||||
result <- fmt.Sprint(nat)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result <- "Discover Error"
|
||||
}
|
||||
} else {
|
||||
result <- "DialUDP Error"
|
||||
}
|
||||
close(result)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-timeout.C:
|
||||
stunText = "Timeout"
|
||||
case r := <-result:
|
||||
stunText = r
|
||||
}
|
||||
}
|
||||
|
||||
fr := make([]string, 0)
|
||||
if latency != "" {
|
||||
fr = append(fr, fmt.Sprintf("Latency: %s", latency))
|
||||
}
|
||||
if speed != "" {
|
||||
fr = append(fr, fmt.Sprintf("Speed: %s", speed))
|
||||
}
|
||||
if in_ip != "" {
|
||||
fr = append(fr, fmt.Sprintf("In: %s", in_ip))
|
||||
}
|
||||
if out_ip != "" {
|
||||
fr = append(fr, fmt.Sprintf("Out: %s", out_ip))
|
||||
}
|
||||
if stunText != "" {
|
||||
fr = append(fr, fmt.Sprintf("NAT: %s", stunText))
|
||||
}
|
||||
|
||||
out.FullReport = strings.Join(fr, " / ")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *server) QueryStats(ctx context.Context, in *gen.QueryStatsReq) (out *gen.QueryStatsResp, _ error) {
|
||||
out = &gen.QueryStatsResp{}
|
||||
if instance != nil {
|
||||
out.Traffic = instance.QueryStats(in.Tag, in.Direct)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *server) ListV2RayConnections(ctx context.Context, in *gen.EmptyReq) (*gen.ListV2RayConnectionsResp, error) {
|
||||
out := &gen.ListV2RayConnectionsResp{
|
||||
MatsuriConnectionsJson: libcore.ListV2rayConnections(),
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
1181
go/gen/libcore.pb.go
Normal file
1181
go/gen/libcore.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
96
go/gen/libcore.proto
Normal file
96
go/gen/libcore.proto
Normal file
@@ -0,0 +1,96 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package libcore;
|
||||
option go_package = "nekoray_core/gen";
|
||||
|
||||
service LibcoreService {
|
||||
rpc Exit(EmptyReq) returns (EmptyResp) {}
|
||||
rpc KeepAlive(EmptyReq) returns (EmptyResp) {}
|
||||
rpc Update(UpdateReq) returns (UpdateResp) {}
|
||||
//
|
||||
rpc Start(LoadConfigReq) returns (ErrorResp) {}
|
||||
rpc SetTun(SetTunReq) returns (ErrorResp) {}
|
||||
rpc Stop(EmptyReq) returns (ErrorResp) {}
|
||||
rpc Test(TestReq) returns (TestResp) {}
|
||||
rpc QueryStats(QueryStatsReq) returns (QueryStatsResp) {}
|
||||
rpc ListV2rayConnections(EmptyReq) returns (ListV2rayConnectionsResp) {}
|
||||
}
|
||||
|
||||
message EmptyReq {}
|
||||
|
||||
message EmptyResp {}
|
||||
|
||||
message ErrorResp {
|
||||
string error = 1;
|
||||
}
|
||||
|
||||
message LoadConfigReq {
|
||||
string coreConfig = 1;
|
||||
string tryDomains = 2;
|
||||
}
|
||||
|
||||
message SetTunReq {
|
||||
string name = 1;
|
||||
int32 mtu = 2;
|
||||
int32 implementation = 3;
|
||||
bool fakedns = 4;
|
||||
}
|
||||
|
||||
enum TestMode {
|
||||
TcpPing = 0;
|
||||
UrlTest = 1;
|
||||
FullTest = 2;
|
||||
}
|
||||
|
||||
message TestReq {
|
||||
TestMode mode = 1;
|
||||
int32 timeout = 6;
|
||||
// TcpPing
|
||||
string address = 2;
|
||||
// UrlTest
|
||||
LoadConfigReq config = 3;
|
||||
string inbound = 4;
|
||||
string url = 5;
|
||||
// FullTest
|
||||
string in_address = 7;
|
||||
bool full_latency = 8;
|
||||
bool full_speed = 9;
|
||||
bool full_in_out = 10;
|
||||
bool full_nat = 11;
|
||||
}
|
||||
|
||||
message TestResp {
|
||||
string error = 1;
|
||||
int32 ms = 2;
|
||||
string full_report = 3;
|
||||
}
|
||||
|
||||
message QueryStatsReq{
|
||||
string tag = 1;
|
||||
string direct = 2;
|
||||
}
|
||||
|
||||
message QueryStatsResp{
|
||||
int64 traffic = 1;
|
||||
}
|
||||
|
||||
enum UpdateAction {
|
||||
Check = 0;
|
||||
Download = 1;
|
||||
}
|
||||
|
||||
message UpdateReq {
|
||||
UpdateAction action = 1;
|
||||
}
|
||||
|
||||
message UpdateResp {
|
||||
string error = 1;
|
||||
string assets_name = 2;
|
||||
string download_url = 3;
|
||||
string release_url = 4;
|
||||
string release_note = 5;
|
||||
}
|
||||
|
||||
message ListV2rayConnectionsResp {
|
||||
string matsuri_connections_json = 1;
|
||||
}
|
||||
395
go/gen/libcore_grpc.pb.go
Normal file
395
go/gen/libcore_grpc.pb.go
Normal file
@@ -0,0 +1,395 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.2.0
|
||||
// - protoc v3.21.4
|
||||
// source: libcore.proto
|
||||
|
||||
package gen
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// LibcoreServiceClient is the client API for LibcoreService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type LibcoreServiceClient interface {
|
||||
Exit(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*EmptyResp, error)
|
||||
KeepAlive(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*EmptyResp, error)
|
||||
Update(ctx context.Context, in *UpdateReq, opts ...grpc.CallOption) (*UpdateResp, error)
|
||||
//
|
||||
Start(ctx context.Context, in *LoadConfigReq, opts ...grpc.CallOption) (*ErrorResp, error)
|
||||
SetTun(ctx context.Context, in *SetTunReq, opts ...grpc.CallOption) (*ErrorResp, error)
|
||||
Stop(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*ErrorResp, error)
|
||||
Test(ctx context.Context, in *TestReq, opts ...grpc.CallOption) (*TestResp, error)
|
||||
QueryStats(ctx context.Context, in *QueryStatsReq, opts ...grpc.CallOption) (*QueryStatsResp, error)
|
||||
ListV2RayConnections(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*ListV2RayConnectionsResp, error)
|
||||
}
|
||||
|
||||
type libcoreServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewLibcoreServiceClient(cc grpc.ClientConnInterface) LibcoreServiceClient {
|
||||
return &libcoreServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *libcoreServiceClient) Exit(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*EmptyResp, error) {
|
||||
out := new(EmptyResp)
|
||||
err := c.cc.Invoke(ctx, "/libcore.LibcoreService/Exit", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *libcoreServiceClient) KeepAlive(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*EmptyResp, error) {
|
||||
out := new(EmptyResp)
|
||||
err := c.cc.Invoke(ctx, "/libcore.LibcoreService/KeepAlive", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *libcoreServiceClient) Update(ctx context.Context, in *UpdateReq, opts ...grpc.CallOption) (*UpdateResp, error) {
|
||||
out := new(UpdateResp)
|
||||
err := c.cc.Invoke(ctx, "/libcore.LibcoreService/Update", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *libcoreServiceClient) Start(ctx context.Context, in *LoadConfigReq, opts ...grpc.CallOption) (*ErrorResp, error) {
|
||||
out := new(ErrorResp)
|
||||
err := c.cc.Invoke(ctx, "/libcore.LibcoreService/Start", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *libcoreServiceClient) SetTun(ctx context.Context, in *SetTunReq, opts ...grpc.CallOption) (*ErrorResp, error) {
|
||||
out := new(ErrorResp)
|
||||
err := c.cc.Invoke(ctx, "/libcore.LibcoreService/SetTun", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *libcoreServiceClient) Stop(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*ErrorResp, error) {
|
||||
out := new(ErrorResp)
|
||||
err := c.cc.Invoke(ctx, "/libcore.LibcoreService/Stop", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *libcoreServiceClient) Test(ctx context.Context, in *TestReq, opts ...grpc.CallOption) (*TestResp, error) {
|
||||
out := new(TestResp)
|
||||
err := c.cc.Invoke(ctx, "/libcore.LibcoreService/Test", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *libcoreServiceClient) QueryStats(ctx context.Context, in *QueryStatsReq, opts ...grpc.CallOption) (*QueryStatsResp, error) {
|
||||
out := new(QueryStatsResp)
|
||||
err := c.cc.Invoke(ctx, "/libcore.LibcoreService/QueryStats", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *libcoreServiceClient) ListV2RayConnections(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*ListV2RayConnectionsResp, error) {
|
||||
out := new(ListV2RayConnectionsResp)
|
||||
err := c.cc.Invoke(ctx, "/libcore.LibcoreService/ListV2rayConnections", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// LibcoreServiceServer is the server API for LibcoreService service.
|
||||
// All implementations must embed UnimplementedLibcoreServiceServer
|
||||
// for forward compatibility
|
||||
type LibcoreServiceServer interface {
|
||||
Exit(context.Context, *EmptyReq) (*EmptyResp, error)
|
||||
KeepAlive(context.Context, *EmptyReq) (*EmptyResp, error)
|
||||
Update(context.Context, *UpdateReq) (*UpdateResp, error)
|
||||
//
|
||||
Start(context.Context, *LoadConfigReq) (*ErrorResp, error)
|
||||
SetTun(context.Context, *SetTunReq) (*ErrorResp, error)
|
||||
Stop(context.Context, *EmptyReq) (*ErrorResp, error)
|
||||
Test(context.Context, *TestReq) (*TestResp, error)
|
||||
QueryStats(context.Context, *QueryStatsReq) (*QueryStatsResp, error)
|
||||
ListV2RayConnections(context.Context, *EmptyReq) (*ListV2RayConnectionsResp, error)
|
||||
mustEmbedUnimplementedLibcoreServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedLibcoreServiceServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedLibcoreServiceServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedLibcoreServiceServer) Exit(context.Context, *EmptyReq) (*EmptyResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Exit not implemented")
|
||||
}
|
||||
func (UnimplementedLibcoreServiceServer) KeepAlive(context.Context, *EmptyReq) (*EmptyResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method KeepAlive not implemented")
|
||||
}
|
||||
func (UnimplementedLibcoreServiceServer) Update(context.Context, *UpdateReq) (*UpdateResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Update not implemented")
|
||||
}
|
||||
func (UnimplementedLibcoreServiceServer) Start(context.Context, *LoadConfigReq) (*ErrorResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Start not implemented")
|
||||
}
|
||||
func (UnimplementedLibcoreServiceServer) SetTun(context.Context, *SetTunReq) (*ErrorResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SetTun not implemented")
|
||||
}
|
||||
func (UnimplementedLibcoreServiceServer) Stop(context.Context, *EmptyReq) (*ErrorResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Stop not implemented")
|
||||
}
|
||||
func (UnimplementedLibcoreServiceServer) Test(context.Context, *TestReq) (*TestResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Test not implemented")
|
||||
}
|
||||
func (UnimplementedLibcoreServiceServer) QueryStats(context.Context, *QueryStatsReq) (*QueryStatsResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method QueryStats not implemented")
|
||||
}
|
||||
func (UnimplementedLibcoreServiceServer) ListV2RayConnections(context.Context, *EmptyReq) (*ListV2RayConnectionsResp, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ListV2RayConnections not implemented")
|
||||
}
|
||||
func (UnimplementedLibcoreServiceServer) mustEmbedUnimplementedLibcoreServiceServer() {}
|
||||
|
||||
// UnsafeLibcoreServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to LibcoreServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeLibcoreServiceServer interface {
|
||||
mustEmbedUnimplementedLibcoreServiceServer()
|
||||
}
|
||||
|
||||
func RegisterLibcoreServiceServer(s grpc.ServiceRegistrar, srv LibcoreServiceServer) {
|
||||
s.RegisterService(&LibcoreService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _LibcoreService_Exit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(EmptyReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(LibcoreServiceServer).Exit(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/libcore.LibcoreService/Exit",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(LibcoreServiceServer).Exit(ctx, req.(*EmptyReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _LibcoreService_KeepAlive_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(EmptyReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(LibcoreServiceServer).KeepAlive(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/libcore.LibcoreService/KeepAlive",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(LibcoreServiceServer).KeepAlive(ctx, req.(*EmptyReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _LibcoreService_Update_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(UpdateReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(LibcoreServiceServer).Update(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/libcore.LibcoreService/Update",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(LibcoreServiceServer).Update(ctx, req.(*UpdateReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _LibcoreService_Start_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(LoadConfigReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(LibcoreServiceServer).Start(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/libcore.LibcoreService/Start",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(LibcoreServiceServer).Start(ctx, req.(*LoadConfigReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _LibcoreService_SetTun_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SetTunReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(LibcoreServiceServer).SetTun(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/libcore.LibcoreService/SetTun",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(LibcoreServiceServer).SetTun(ctx, req.(*SetTunReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _LibcoreService_Stop_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(EmptyReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(LibcoreServiceServer).Stop(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/libcore.LibcoreService/Stop",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(LibcoreServiceServer).Stop(ctx, req.(*EmptyReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _LibcoreService_Test_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(TestReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(LibcoreServiceServer).Test(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/libcore.LibcoreService/Test",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(LibcoreServiceServer).Test(ctx, req.(*TestReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _LibcoreService_QueryStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(QueryStatsReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(LibcoreServiceServer).QueryStats(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/libcore.LibcoreService/QueryStats",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(LibcoreServiceServer).QueryStats(ctx, req.(*QueryStatsReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _LibcoreService_ListV2RayConnections_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(EmptyReq)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(LibcoreServiceServer).ListV2RayConnections(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/libcore.LibcoreService/ListV2rayConnections",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(LibcoreServiceServer).ListV2RayConnections(ctx, req.(*EmptyReq))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// LibcoreService_ServiceDesc is the grpc.ServiceDesc for LibcoreService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var LibcoreService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "libcore.LibcoreService",
|
||||
HandlerType: (*LibcoreServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Exit",
|
||||
Handler: _LibcoreService_Exit_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "KeepAlive",
|
||||
Handler: _LibcoreService_KeepAlive_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Update",
|
||||
Handler: _LibcoreService_Update_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Start",
|
||||
Handler: _LibcoreService_Start_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "SetTun",
|
||||
Handler: _LibcoreService_SetTun_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Stop",
|
||||
Handler: _LibcoreService_Stop_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Test",
|
||||
Handler: _LibcoreService_Test_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "QueryStats",
|
||||
Handler: _LibcoreService_QueryStats_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListV2rayConnections",
|
||||
Handler: _LibcoreService_ListV2RayConnections_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "libcore.proto",
|
||||
}
|
||||
3
go/gen/update_proto.sh
Normal file
3
go/gen/update_proto.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
protoc -I . --go_out=. --go_opt paths=source_relative --go-grpc_out=. --go-grpc_opt paths=source_relative libcore.proto
|
||||
|
||||
# protoc -I . --cpp_out=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` libcore.proto
|
||||
93
go/go.mod
Normal file
93
go/go.mod
Normal file
@@ -0,0 +1,93 @@
|
||||
module nekoray_core
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0
|
||||
github.com/jsimonetti/rtnetlink v1.2.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/v2fly/v2ray-core/v5 v5.0.0
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
|
||||
google.golang.org/grpc v1.48.0
|
||||
google.golang.org/protobuf v1.28.1
|
||||
kernel.org/pub/linux/libs/security/libcap/cap v1.2.64
|
||||
libcore v1.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Dreamacro/clash v1.9.0 // indirect
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7 // indirect
|
||||
github.com/adrg/xdg v0.4.0 // indirect
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
||||
github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1 // indirect
|
||||
github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d // indirect
|
||||
github.com/cheekybits/genny v1.0.0 // indirect
|
||||
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d // indirect
|
||||
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb // indirect
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
||||
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/geeksbaek/seed v0.0.0-20180909040025-2a7f5fb92e22 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/btree v1.0.1 // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/jhump/protoreflect v1.12.0 // indirect
|
||||
github.com/josharian/native v1.0.0 // indirect
|
||||
github.com/kierdavis/cfb8 v0.0.0-20180105024805-3a17c36ee2f8 // indirect
|
||||
github.com/klauspost/cpuid v1.2.3 // indirect
|
||||
github.com/klauspost/reedsolomon v1.9.3 // indirect
|
||||
github.com/lucas-clemente/quic-go v0.28.1 // indirect
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 // indirect
|
||||
github.com/mdlayher/netlink v1.6.0 // indirect
|
||||
github.com/mdlayher/socket v0.1.1 // indirect
|
||||
github.com/miekg/dns v1.1.50 // indirect
|
||||
github.com/mustafaturan/bus v1.0.2 // indirect
|
||||
github.com/mustafaturan/monoton v1.0.0 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pion/dtls/v2 v2.0.0-rc.7 // indirect
|
||||
github.com/pion/logging v0.2.2 // indirect
|
||||
github.com/pion/sctp v1.7.6 // indirect
|
||||
github.com/pires/go-proxyproto v0.6.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
github.com/sagernet/gomobile v0.0.0-20210905032500-701a995ff844 // indirect
|
||||
github.com/sagernet/libping v0.1.1 // indirect
|
||||
github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4 // indirect
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220312154859-af7fbb8e765b // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08 // indirect
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
||||
github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432 // indirect
|
||||
github.com/xtaci/smux v1.5.16 // indirect
|
||||
go.starlark.net v0.0.0-20220302181546-5411bad688d1 // indirect
|
||||
go.uber.org/automaxprocs v1.4.0 // indirect
|
||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 // indirect
|
||||
golang.org/x/mod v0.5.1 // indirect
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect
|
||||
golang.org/x/tools v0.1.9 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gvisor.dev/gvisor v0.0.0 // indirect
|
||||
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6 // indirect
|
||||
kernel.org/pub/linux/libs/security/libcap/psx v1.2.64 // indirect
|
||||
)
|
||||
|
||||
replace libcore v1.0.0 => ../../Matsuri/libcore
|
||||
|
||||
replace github.com/v2fly/v2ray-core/v5 v5.0.0 => ../../v2ray-core
|
||||
|
||||
replace gvisor.dev/gvisor => github.com/sagernet/gvisor v0.0.0-20220402114650-763d12dc953e
|
||||
737
go/go.sum
Normal file
737
go/go.sum
Normal file
@@ -0,0 +1,737 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3 h1:AVXDdKsrtX33oR9fbCMu/+c1o8Ofjq6Ku/MInaLVg5Y=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Dreamacro/clash v1.9.0 h1:IfmPW86Klngu0iQ4LL6Bhxcvtr+QaI7Oppa9qRPX/Q8=
|
||||
github.com/Dreamacro/clash v1.9.0/go.mod h1:vOzDB9KKD/PirNdSlsH4soMl1xF5lk8SwNQiVY5UacE=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7 h1:8CtbE1HoPPMfrQZGXmlluq6dO2lL31W6WRRE8fabc4Q=
|
||||
github.com/Dreamacro/go-shadowsocks2 v0.1.7/go.mod h1:8p5G4cAj5ZlXwUR+Ww63gfSikr8kvw8uw3TDwLAJpUc=
|
||||
github.com/FlowerWrong/water v0.0.0-20180301012659-01a4eaa1f6f2/go.mod h1:xrG5L7lq7T2DLnPr2frMnL906CNEoKRwLB+VYFhPq2w=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
|
||||
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||
github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1 h1:+JkXLHME8vLJafGhOH4aoV2Iu8bR55nU6iKMVfYVLjY=
|
||||
github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1/go.mod h1:nuudZmJhzWtx2212z+pkuy7B6nkBqa+xwNXZHL1j8cg=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d h1:zsO4lp+bjv5XvPTF58Vq+qgmZEYZttJK+CWtSZhKenI=
|
||||
github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d/go.mod h1:f1iKL6ZhUWvbk7PdWVmOaak10o86cqMUYEmn1CZNGEI=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cilium/ebpf v0.8.1 h1:bLSSEbBLqGPXxls55pGr5qWZaTqcmfDJHhou7t254ao=
|
||||
github.com/cilium/ebpf v0.8.1/go.mod h1:f5zLIM0FSNuAkSyLAN7X+Hy6yznlF1mNiWUMfxMtrgk=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d h1:CPqTNIigGweVPT4CYb+OO2E6XyRKFOmvTHwWRLgCAlE=
|
||||
github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d/go.mod h1:QX5ZVULjAfZJux/W62Y91HvCh9hyW6enAwcrrv/sLj0=
|
||||
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb h1:zXpN5126w/mhECTkqazBkrOJIMatbPP71aSIDR5UuW4=
|
||||
github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb/go.mod h1:F7WkpqJj9t98ePxB/WJGQTIDeOVPuSJ3qdn6JUjg170=
|
||||
github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152 h1:ED31mPIxDJnrLt9W9dH5xgd/6KjzEACKHBVGQ33czc0=
|
||||
github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152/go.mod h1:I9fhc/EvSg88cDxmfQ47v35Ssz9rlFunL/KY0A1JAYI=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
||||
github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 h1:fBHFH+Y/GPGFGo7LIrErQc3p2MeAhoIQNgaxPWYsSxk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/geeksbaek/seed v0.0.0-20180909040025-2a7f5fb92e22 h1:CdVtqYWYGIEuYCbtyx6BVMKOcO0N6lKm99cR1DZubAs=
|
||||
github.com/geeksbaek/seed v0.0.0-20180909040025-2a7f5fb92e22/go.mod h1:YS1s0XuwU13tHT0WeYeUXUwGk1m8WZvSbK9cx/kY1SE=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang-collections/go-datastructures v0.0.0-20150211160725-59788d5eb259/go.mod h1:9Qcha0gTWLw//0VNka1Cbnjvg3pNKGFdAm7E9sBabxE=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20210420193930-a4630ec28c79/go.mod h1:Opf9rtYVq0eTyX+aRVmRO9hE8ERAozcdrBxWG9Q6mkQ=
|
||||
github.com/gopherjs/websocket v0.0.0-20191103002815-9a42957e2b3a/go.mod h1:jd+zY81Fx2lC4bfw58+Rflg1srqmedQjbBUejKOjYNY=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||
github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI=
|
||||
github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI=
|
||||
github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ=
|
||||
github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E=
|
||||
github.com/jhump/protoreflect v1.12.0 h1:1NQ4FpWMgn3by/n1X0fbeKEUxP1wBt7+Oitpv01HR10=
|
||||
github.com/jhump/protoreflect v1.12.0/go.mod h1:JytZfP5d0r8pVNLZvai7U/MCuTWITgrI4tTg7puQFKI=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
|
||||
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/jsimonetti/rtnetlink v1.2.0 h1:KlwYLoRXgirTFbh1aVI6MJ7i+R/zJr+JkyhlIW1X3z4=
|
||||
github.com/jsimonetti/rtnetlink v1.2.0/go.mod h1:RA0RtDj3hv4g6l/Y4B7RubIQkdTDAwXfMW/8bMaZ0FY=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kierdavis/cfb8 v0.0.0-20180105024805-3a17c36ee2f8 h1:QxgFSDEqLP8ZsmVm/Qke0HP6JLV7EB93vtWK7noU1Sw=
|
||||
github.com/kierdavis/cfb8 v0.0.0-20180105024805-3a17c36ee2f8/go.mod h1:uL2TcUivilrs0kPsqUwIf8XHAcmkSjsfrzSgAJwS0TI=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs=
|
||||
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/reedsolomon v1.9.3 h1:N/VzgeMfHmLc+KHMD1UL/tNkfXAt8FnUqlgXGIduwAY=
|
||||
github.com/klauspost/reedsolomon v1.9.3/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lucas-clemente/quic-go v0.28.1 h1:Uo0lvVxWg5la9gflIF9lwa39ONq85Xq2D91YNEIslzU=
|
||||
github.com/lucas-clemente/quic-go v0.28.1/go.mod h1:oGz5DKK41cJt5+773+BSO9BXDsREY4HLf7+0odGAPO0=
|
||||
github.com/lunixbochs/struc v0.0.0-20190916212049-a5c72983bc42/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.2 h1:JADBlm0LYiVbuSySCHeY863dNkcpMmDR7s0bLKJeYlQ=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.2/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1 h1:7m/WlWcSROrcK5NxuXaxYD32BZqe/LEEnBrWcH/cOqQ=
|
||||
github.com/marten-seemann/qtls-go1-19 v0.1.0-beta.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mdlayher/netlink v1.6.0 h1:rOHX5yl7qnlpiVkFWoqccueppMtXzeziFjWAjLg6sz0=
|
||||
github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA=
|
||||
github.com/mdlayher/socket v0.1.1 h1:q3uOGirUPfAV2MUoaC7BavjQ154J7+JOkTWyiV+intI=
|
||||
github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mustafaturan/bus v1.0.2 h1:2x3ErwZ0uUPwwZ5ZZoknEQprdaxr68Yl3mY8jDye1Ws=
|
||||
github.com/mustafaturan/bus v1.0.2/go.mod h1:h7gfehm8TThv4Dcaa+wDQG7r7j6p74v+7ftr0Rq9i1Q=
|
||||
github.com/mustafaturan/monoton v0.3.1/go.mod h1:FOnE7NV3s3EWPXb8/7+/OSdiMBbdlkV0Lz8p1dc+vy8=
|
||||
github.com/mustafaturan/monoton v1.0.0 h1:8SCej+JiNn0lyps7V+Jzc1CRAkDR4EZPWrTupQ61YCQ=
|
||||
github.com/mustafaturan/monoton v1.0.0/go.mod h1:FOnE7NV3s3EWPXb8/7+/OSdiMBbdlkV0Lz8p1dc+vy8=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
|
||||
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pion/dtls/v2 v2.0.0-rc.7 h1:LDAIQDt1pcuAIJs7Q2EZ3PSl8MseCFA2nCW0YYSYCx0=
|
||||
github.com/pion/dtls/v2 v2.0.0-rc.7/go.mod h1:U199DvHpRBN0muE9+tVN4TMy1jvEhZIZ63lk4xkvVSk=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/sctp v1.7.6 h1:8qZTdJtbKfAns/Hv5L0PAj8FyXcsKhMH1pKUCGisQg4=
|
||||
github.com/pion/sctp v1.7.6/go.mod h1:ichkYQ5tlgCQwEwvgfdcAolqx1nHbYCxo4D7zK/K0X8=
|
||||
github.com/pion/transport v0.8.10 h1:lTiobMEw2PG6BH/mgIVqTV2mBp/mPT+IJLaN8ZxgdHk=
|
||||
github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8=
|
||||
github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
|
||||
github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sagernet/gomobile v0.0.0-20210905032500-701a995ff844 h1:o7izBZde2L5foPbQdYisY03y4+6T6UcUXOwR5MyQpUk=
|
||||
github.com/sagernet/gomobile v0.0.0-20210905032500-701a995ff844/go.mod h1:2Xj8wyq0y6G6B1gCNTzRcKqo+cyVKatMTNWUmxNYfI4=
|
||||
github.com/sagernet/gvisor v0.0.0-20220402114650-763d12dc953e h1:Y4avBAtZ59OWvLl6zP9sF62jtMEVRPIH78IQctq9aXQ=
|
||||
github.com/sagernet/gvisor v0.0.0-20220402114650-763d12dc953e/go.mod h1:tWwEcFvJavs154OdjFCw78axNrsDlz4Zh8jvPqwcpGI=
|
||||
github.com/sagernet/libping v0.1.1 h1:uNMN/02fQmRbsgJ0EuxuM/Upq8FrVP4Xj2+LlYviIOs=
|
||||
github.com/sagernet/libping v0.1.1/go.mod h1:FhmyYM8L32JaKI08noqoS5cK+Gw/Q+4VDnI9WvP6Sp8=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4 h1:zOjq+1/uLzn/Xo40stbvjIY/yehG0+mfmlsiEmc0xmQ=
|
||||
github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4/go.mod h1:aI+8yClBW+1uovkHw6HM01YXnYB8vohtB9C83wzx34E=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20200416141329-862a88987de7/go.mod h1:ET5mVvNjwaGXRgZxO9UZr7X+8eAf87AfIYNwRSp9s4Y=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220312154859-af7fbb8e765b h1:wHoB6ZYEnIVizebcj419LbN4Tagk7RDFiudRFKyzzmo=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220312154859-af7fbb8e765b/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
|
||||
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
|
||||
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
||||
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
||||
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
|
||||
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
|
||||
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
|
||||
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
|
||||
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
|
||||
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
|
||||
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/txthinking/runnergroup v0.0.0-20200327135940-540a793bb997/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM=
|
||||
github.com/txthinking/socks5 v0.0.0-20200327133705-caf148ab5e9d/go.mod h1:d3n8NJ6QMRb6I/WAlp4z5ZPAoaeqDmX5NgVZA0mhe+I=
|
||||
github.com/txthinking/x v0.0.0-20200330144832-5ad2416896a9/go.mod h1:WgqbSEmUYSjEV3B1qmee/PpP2NYEz4bL9/+mF1ma+s4=
|
||||
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
|
||||
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08 h1:4Yh46CVE3k/lPq6hUbEdbB1u1anRBXLewm3k+L0iOMc=
|
||||
github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08/go.mod h1:KAuQNm+LWQCOFqdBcUgihPzRpVXRKzGbTNhfEfRZ4wY=
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432 h1:I/ATawgO2RerCq9ACwL0wBB8xNXZdE3J+93MCEHReRs=
|
||||
github.com/xiaokangwang/VLite v0.0.0-20220418190619-cff95160a432/go.mod h1:QN7Go2ftTVfx0aCTh9RXHV8pkpi0FtmbwQw40dy61wQ=
|
||||
github.com/xtaci/smux v1.5.12/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
|
||||
github.com/xtaci/smux v1.5.15/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
|
||||
github.com/xtaci/smux v1.5.16 h1:FBPYOkW8ZTjLKUM4LI4xnnuuDC8CQ/dB04HD519WoEk=
|
||||
github.com/xtaci/smux v1.5.16/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.starlark.net v0.0.0-20220302181546-5411bad688d1 h1:i0Sz4b+qJi5xwOaFZqZ+RNHkIpaKLDofei/Glt+PMNc=
|
||||
go.starlark.net v0.0.0-20220302181546-5411bad688d1/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0=
|
||||
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE=
|
||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 h1:Tx9kY6yUkLge/pFG7IEMwDZy6CS2ajFc9TvQdPCW0uA=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 h1:S25/rfnfsMVgORT4/J61MJ7rdyseOZOyvLIrZEZ7s6s=
|
||||
golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 h1:B333XXssMuKQeBwiNODx4TupZy7bf4sxFZnN2ZOcvUE=
|
||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M=
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8=
|
||||
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb h1:ZrsicilzPCS/Xr8qtBZZLpy4P9TYXAfl49ctG1/5tgw=
|
||||
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w=
|
||||
google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6 h1:acCzuUSQ79tGsM/O50VRFySfMm19IoMKL+sZztZkCxw=
|
||||
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6/go.mod h1:y3MGhcFMlh0KZPMuXXow8mpjxxAk3yoDNsp4cQz54i8=
|
||||
kernel.org/pub/linux/libs/security/libcap/cap v1.2.64 h1:E1U4GNGSXEdzQUT+mop0iYawCNXDUU46Y8nfodb+ZY0=
|
||||
kernel.org/pub/linux/libs/security/libcap/cap v1.2.64/go.mod h1:gtBlgvjXflnxHng9/3bXyXG3XmBYKDt35zu+lNmB+IA=
|
||||
kernel.org/pub/linux/libs/security/libcap/psx v1.2.64 h1:zlw/KoDjEObyddpFcvLiuu8frEvyEwVNc62WZQBp68w=
|
||||
kernel.org/pub/linux/libs/security/libcap/psx v1.2.64/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||
223
go/grpc.go
Normal file
223
go/grpc.go
Normal file
@@ -0,0 +1,223 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"libcore"
|
||||
"log"
|
||||
"nekoray_core/gen"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
_ "unsafe"
|
||||
|
||||
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
|
||||
v2rayNet "github.com/v2fly/v2ray-core/v5/common/net"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
gen.LibcoreServiceServer
|
||||
}
|
||||
|
||||
var last time.Time
|
||||
var nekoray_debug bool
|
||||
|
||||
func (s *server) KeepAlive(ctx context.Context, in *gen.EmptyReq) (*gen.EmptyResp, error) {
|
||||
last = time.Now()
|
||||
return &gen.EmptyResp{}, nil
|
||||
}
|
||||
|
||||
func NekorayCore() {
|
||||
_token := flag.String("token", "", "")
|
||||
_port := flag.Int("port", 19810, "")
|
||||
_debug := flag.Bool("debug", false, "")
|
||||
flag.CommandLine.Parse(os.Args[2:])
|
||||
|
||||
nekoray_debug = *_debug
|
||||
|
||||
go func() {
|
||||
t := time.NewTicker(time.Second * 10)
|
||||
for {
|
||||
<-t.C
|
||||
if last.Add(time.Second * 10).Before(time.Now()) {
|
||||
fmt.Println("Exit due to inactive")
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Libcore
|
||||
setupCore()
|
||||
|
||||
// GRPC
|
||||
lis, err := net.Listen("tcp", "127.0.0.1:"+strconv.Itoa(*_port))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
|
||||
token := *_token
|
||||
if token == "" {
|
||||
os.Stderr.WriteString("Please set a token: ")
|
||||
s := bufio.NewScanner(os.Stdin)
|
||||
if s.Scan() {
|
||||
token = strings.TrimSpace(s.Text())
|
||||
}
|
||||
}
|
||||
if token == "" {
|
||||
fmt.Println("You must set a token")
|
||||
os.Exit(0)
|
||||
}
|
||||
os.Stderr.WriteString("token is set\n")
|
||||
|
||||
auther := Authenticator{
|
||||
Token: token,
|
||||
}
|
||||
|
||||
s := grpc.NewServer(
|
||||
grpc.StreamInterceptor(grpc_auth.StreamServerInterceptor(auther.Authenticate)),
|
||||
grpc.UnaryInterceptor(grpc_auth.UnaryServerInterceptor(auther.Authenticate)),
|
||||
)
|
||||
gen.RegisterLibcoreServiceServer(s, &server{})
|
||||
|
||||
log.Printf("neokray grpc server listening at %v", lis.Addr())
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// PROXY
|
||||
|
||||
func getProxyHttpClient(_instance *libcore.V2RayInstance) *http.Client {
|
||||
dailContext := func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
dest, err := v2rayNet.ParseDestination(fmt.Sprintf("%s:%s", network, addr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return _instance.DialContext(ctx, dest)
|
||||
}
|
||||
|
||||
transport := &http.Transport{
|
||||
TLSHandshakeTimeout: time.Second * 3,
|
||||
ResponseHeaderTimeout: time.Second * 3,
|
||||
}
|
||||
if _instance != nil {
|
||||
transport.DialContext = dailContext
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
// UPDATE
|
||||
|
||||
var update_download_url, update_download_as string
|
||||
|
||||
func (s *server) Update(ctx context.Context, in *gen.UpdateReq) (*gen.UpdateResp, error) {
|
||||
ret := &gen.UpdateResp{}
|
||||
|
||||
client := getProxyHttpClient(instance)
|
||||
|
||||
if in.Action == gen.UpdateAction_Check { // Check update
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Second*10)
|
||||
defer cancel()
|
||||
|
||||
req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.github.com/repos/MatsuriDayo/nekoray/releases", nil)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
ret.Error = err.Error()
|
||||
return ret, nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
v := []struct {
|
||||
HtmlUrl string `json:"html_url"`
|
||||
Assets []struct {
|
||||
Name string `json:"name"`
|
||||
BrowserDownloadUrl string `json:"browser_download_url"`
|
||||
} `json:"assets"`
|
||||
Prerelease bool `json:"prerelease"`
|
||||
Body string `json:"body"`
|
||||
}{}
|
||||
err = json.NewDecoder(resp.Body).Decode(&v)
|
||||
if err != nil {
|
||||
ret.Error = err.Error()
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
nowVer := strings.TrimLeft(version_standalone, "nekoray-")
|
||||
|
||||
var search string
|
||||
if runtime.GOOS == "windows" && runtime.GOARCH == "amd64" {
|
||||
search = "windows64"
|
||||
update_download_as = "nekoray.zip"
|
||||
} else if runtime.GOOS == "linux" && runtime.GOARCH == "amd64" {
|
||||
search = "linux64"
|
||||
update_download_as = "nekoray.tar.gz"
|
||||
} else {
|
||||
ret.Error = "Not official support platform"
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
for _, release := range v {
|
||||
if len(release.Assets) > 0 {
|
||||
for _, asset := range release.Assets {
|
||||
if strings.Contains(asset.Name, nowVer) {
|
||||
return ret, nil // No update
|
||||
}
|
||||
if strings.Contains(asset.Name, search) {
|
||||
if release.Prerelease {
|
||||
continue
|
||||
}
|
||||
update_download_url = asset.BrowserDownloadUrl
|
||||
ret.AssetsName = asset.Name
|
||||
ret.DownloadUrl = asset.BrowserDownloadUrl
|
||||
ret.ReleaseUrl = release.HtmlUrl
|
||||
ret.ReleaseNote = release.Body
|
||||
return ret, nil // update
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else { // Download update
|
||||
if update_download_url == "" || update_download_as == "" {
|
||||
ret.Error = "?"
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
req, _ := http.NewRequestWithContext(ctx, "GET", update_download_url, nil)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
ret.Error = err.Error()
|
||||
return ret, nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
f, err := os.OpenFile("../"+update_download_as, os.O_TRUNC|os.O_CREATE|os.O_RDWR, 0644)
|
||||
if err != nil {
|
||||
ret.Error = err.Error()
|
||||
return ret, nil
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = io.Copy(f, resp.Body)
|
||||
if err != nil {
|
||||
ret.Error = err.Error()
|
||||
return ret, nil
|
||||
}
|
||||
f.Sync()
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
8
go/import_extra.go
Normal file
8
go/import_extra.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "github.com/v2fly/v2ray-core/v5/proxy/vless/inbound"
|
||||
_ "github.com/v2fly/v2ray-core/v5/proxy/vless/outbound"
|
||||
_ "github.com/v2fly/v2ray-core/v5/proxy/vlite/inbound"
|
||||
_ "github.com/v2fly/v2ray-core/v5/proxy/vlite/outbound"
|
||||
)
|
||||
23
go/iphlpapi/callback_windows.go
Normal file
23
go/iphlpapi/callback_windows.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package iphlpapi
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func notifyRouteChange2(family uint16, callback uintptr, callerContext uintptr, initialNotification bool, notificationHandle *syscall.Handle) (ret error) {
|
||||
var _p0 uint32
|
||||
if initialNotification {
|
||||
_p0 = 1
|
||||
}
|
||||
r0, _, _ := syscall.Syscall6(proc_notifyRouteChange2.Addr(), 5, uintptr(family), uintptr(callback), uintptr(callerContext), uintptr(_p0), uintptr(unsafe.Pointer(notificationHandle)), 0)
|
||||
if r0 != 0 {
|
||||
ret = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func RegisterNotifyRouteChange2(callback func(callerContext uintptr, row uintptr, notificationType uint32) uintptr, initialNotification bool) (handle syscall.Handle) {
|
||||
notifyRouteChange2(syscall.AF_UNSPEC, syscall.NewCallback(callback), 0, initialNotification, &handle)
|
||||
return
|
||||
}
|
||||
14
go/iphlpapi/dll_windows.go
Normal file
14
go/iphlpapi/dll_windows.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package iphlpapi
|
||||
|
||||
import "syscall"
|
||||
|
||||
var (
|
||||
proc_getIpForwardTable *syscall.LazyProc
|
||||
proc_notifyRouteChange2 *syscall.LazyProc
|
||||
)
|
||||
|
||||
func init() {
|
||||
iphlpapi := syscall.NewLazyDLL("iphlpapi.dll")
|
||||
proc_getIpForwardTable = iphlpapi.NewProc("GetIpForwardTable")
|
||||
proc_notifyRouteChange2 = iphlpapi.NewProc("NotifyRouteChange2")
|
||||
}
|
||||
87
go/iphlpapi/route_windows.go
Normal file
87
go/iphlpapi/route_windows.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package iphlpapi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
对于路由表,预期的方法是:
|
||||
查询 0.0.0.0/0 获得原始默认路由
|
||||
然后为 vpn 服务器添加默认路由
|
||||
之后就根据需要下发vpn路由完事。
|
||||
对于0.0.0.0/0 vpn 路由,可以尝试更低的跃点数,也可以尝试分为2个。
|
||||
重新连接时可以删除vpn接口的所有非链路路由表。
|
||||
路由表格式:
|
||||
目标网络 uint32 掩码位数 byte低6位 vpn/默认网关 byte 高1位
|
||||
*/
|
||||
|
||||
// 太低的值添加路由时会返回 106 错误
|
||||
const routeMetric = 93
|
||||
|
||||
type RouteRow struct {
|
||||
ForwardDest [4]byte //目标网络
|
||||
ForwardMask [4]byte //掩码
|
||||
ForwardPolicy uint32 //ForwardPolicy:0x0
|
||||
ForwardNextHop [4]byte //网关
|
||||
ForwardIfIndex uint32 // 网卡索引 id
|
||||
ForwardType uint32 //3 本地接口 4 远端接口
|
||||
ForwardProto uint32 //3静态路由 2本地接口 5EGP网关
|
||||
ForwardAge uint32 //存在时间 秒
|
||||
ForwardNextHopAS uint32 //下一跳自治域号码 0
|
||||
ForwardMetric1 uint32 //度量衡(跃点数),根据 ForwardProto 不同意义不同。
|
||||
ForwardMetric2 uint32
|
||||
ForwardMetric3 uint32
|
||||
ForwardMetric4 uint32
|
||||
ForwardMetric5 uint32
|
||||
}
|
||||
|
||||
func (rr *RouteRow) GetForwardDest() net.IP {
|
||||
return net.IP(rr.ForwardDest[:])
|
||||
}
|
||||
func (rr *RouteRow) GetForwardMask() net.IP {
|
||||
return net.IP(rr.ForwardMask[:])
|
||||
}
|
||||
func (rr *RouteRow) GetForwardNextHop() net.IP {
|
||||
return net.IP(rr.ForwardNextHop[:])
|
||||
}
|
||||
|
||||
func GetRoutes() ([]RouteRow, error) {
|
||||
buf := make([]byte, 4+unsafe.Sizeof(RouteRow{}))
|
||||
buf_len := uint32(len(buf))
|
||||
|
||||
proc_getIpForwardTable.Call(uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(unsafe.Pointer(&buf_len)), 0)
|
||||
|
||||
var r1 uintptr
|
||||
for i := 0; i < 5; i++ {
|
||||
buf = make([]byte, buf_len)
|
||||
r1, _, _ = proc_getIpForwardTable.Call(uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(unsafe.Pointer(&buf_len)), 0)
|
||||
if r1 == 122 {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if r1 != 0 {
|
||||
return nil, fmt.Errorf("Failed to get the routing table, return value:%v", r1)
|
||||
}
|
||||
|
||||
num := *(*uint32)(unsafe.Pointer(&buf[0]))
|
||||
routes := make([]RouteRow, num)
|
||||
sr := uintptr(unsafe.Pointer(&buf[0])) + unsafe.Sizeof(num)
|
||||
rowSize := unsafe.Sizeof(RouteRow{})
|
||||
|
||||
// 安全检查
|
||||
if len(buf) < int((unsafe.Sizeof(num) + rowSize*uintptr(num))) {
|
||||
return nil, fmt.Errorf("System error: GetIpForwardTable returns the number is too long, beyond the buffer。")
|
||||
}
|
||||
|
||||
for i := uint32(0); i < num; i++ {
|
||||
routes[i] = *((*RouteRow)(unsafe.Pointer(sr + (rowSize * uintptr(i)))))
|
||||
}
|
||||
|
||||
return routes, nil
|
||||
}
|
||||
58
go/main.go
Normal file
58
go/main.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v5/main/commands"
|
||||
"github.com/v2fly/v2ray-core/v5/main/commands/base"
|
||||
)
|
||||
|
||||
//go:linkname build github.com/v2fly/v2ray-core/v5.build
|
||||
var build string
|
||||
|
||||
var version_v2ray string = "N/A"
|
||||
var version_standalone string = "N/A"
|
||||
|
||||
func main() {
|
||||
fmt.Println("V2Ray:", version_v2ray, "Version:", version_standalone)
|
||||
fmt.Println()
|
||||
|
||||
// nekoray
|
||||
if len(os.Args) > 1 && os.Args[1] == "nekoray" {
|
||||
NekorayCore()
|
||||
return
|
||||
}
|
||||
|
||||
// toolbox
|
||||
if len(os.Args) > 1 && os.Args[1] == "tool" {
|
||||
ToolBox()
|
||||
return
|
||||
}
|
||||
|
||||
build = "Matsuridayo/Nekoray"
|
||||
main_v2ray_v5()
|
||||
}
|
||||
|
||||
func main_v2ray_v5() {
|
||||
base.RootCommand.Long = "A unified platform for anti-censorship."
|
||||
base.RegisterCommand(commands.CmdRun)
|
||||
base.RegisterCommand(commands.CmdVersion)
|
||||
base.RegisterCommand(commands.CmdTest)
|
||||
base.SortLessFunc = runIsTheFirst
|
||||
base.SortCommands()
|
||||
base.Execute()
|
||||
}
|
||||
|
||||
func runIsTheFirst(i, j *base.Command) bool {
|
||||
left := i.Name()
|
||||
right := j.Name()
|
||||
if left == "run" {
|
||||
return true
|
||||
}
|
||||
if right == "run" {
|
||||
return false
|
||||
}
|
||||
return left < right
|
||||
}
|
||||
139
go/protect_bindinterface_windows.go
Normal file
139
go/protect_bindinterface_windows.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"log"
|
||||
"nekoray_core/iphlpapi"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/v2fly/v2ray-core/v5/transport/internet"
|
||||
)
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/ipmib/ns-ipmib-mib_ipforwardrow
|
||||
var routes []iphlpapi.RouteRow
|
||||
var interfaces []net.Interface
|
||||
var lock sync.Mutex
|
||||
|
||||
func init() {
|
||||
internet.RegisterListenerController(func(network, address string, fd uintptr) error {
|
||||
bindInterfaceIndex := getBindInterfaceIndex()
|
||||
if bindInterfaceIndex != 0 {
|
||||
if err := bindInterface(fd, bindInterfaceIndex, true, true); err != nil {
|
||||
log.Println("bind inbound interface", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
internet.RegisterDialerController(func(network, address string, fd uintptr) error {
|
||||
bindInterfaceIndex := getBindInterfaceIndex()
|
||||
if bindInterfaceIndex != 0 {
|
||||
var v4, v6 bool
|
||||
if strings.HasSuffix(network, "6") {
|
||||
v4 = false
|
||||
v6 = true
|
||||
} else {
|
||||
v4 = true
|
||||
v6 = false
|
||||
}
|
||||
if err := bindInterface(fd, bindInterfaceIndex, v4, v6); err != nil {
|
||||
log.Println("bind outbound interface", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
iphlpapi.RegisterNotifyRouteChange2(func(callerContext uintptr, row uintptr, notificationType uint32) uintptr {
|
||||
updateRoutes()
|
||||
return 0
|
||||
}, true)
|
||||
}
|
||||
|
||||
func updateRoutes() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
var err error
|
||||
routes, err = iphlpapi.GetRoutes()
|
||||
if err != nil {
|
||||
log.Println("warning: GetRoutes failed", err)
|
||||
}
|
||||
interfaces, err = net.Interfaces()
|
||||
if err != nil {
|
||||
log.Println("warning: Interfaces failed", err)
|
||||
}
|
||||
}
|
||||
|
||||
func getBindInterfaceIndex() uint32 {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
if routes == nil {
|
||||
log.Println("warning: no routes info")
|
||||
return 0
|
||||
}
|
||||
if interfaces == nil {
|
||||
log.Println("warning: no interfaces info")
|
||||
return 0
|
||||
}
|
||||
|
||||
var nextInterface int
|
||||
for i, intf := range interfaces {
|
||||
if intf.Name == "nekoray-tun" || intf.Name == "wintun" || intf.Name == "TunMax" {
|
||||
if len(interfaces) > i+1 {
|
||||
nextInterface = interfaces[i+1].Index
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Not in VPN mode
|
||||
if nextInterface == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
// MIB_IPROUTE_TYPE_INDIRECT
|
||||
if route.ForwardType == 4 {
|
||||
// MIB_IPPROTO_NETMGMT
|
||||
if route.ForwardProto == 3 {
|
||||
if route.GetForwardMask().IsUnspecified() {
|
||||
return route.ForwardIfIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default route not found
|
||||
return uint32(nextInterface)
|
||||
}
|
||||
|
||||
const (
|
||||
IP_UNICAST_IF = 31 // nolint: golint,stylecheck
|
||||
IPV6_UNICAST_IF = 31 // nolint: golint,stylecheck
|
||||
)
|
||||
|
||||
func bindInterface(fd uintptr, interfaceIndex uint32, v4, v6 bool) error {
|
||||
if v4 {
|
||||
/* MSDN says for IPv4 this needs to be in net byte order, so that it's like an IP address with leading zeros. */
|
||||
bytes := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(bytes, interfaceIndex)
|
||||
interfaceIndex_v4 := *(*uint32)(unsafe.Pointer(&bytes[0]))
|
||||
|
||||
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IP, IP_UNICAST_IF, int(interfaceIndex_v4)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if v6 {
|
||||
if err := syscall.SetsockoptInt(syscall.Handle(fd), syscall.IPPROTO_IPV6, IPV6_UNICAST_IF, int(interfaceIndex)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
105
go/protect_fwmark_linux.go
Normal file
105
go/protect_fwmark_linux.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"libcore/protect"
|
||||
"log"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/jsimonetti/rtnetlink"
|
||||
linuxcap "kernel.org/pub/linux/libs/security/libcap/cap"
|
||||
)
|
||||
|
||||
type fwmarkProtector struct{}
|
||||
|
||||
var rtnetlink_conn *rtnetlink.Conn
|
||||
var cap_net_admin = 0
|
||||
|
||||
func (f *fwmarkProtector) Protect(fd int32) bool {
|
||||
if cap_net_admin == 0 {
|
||||
str := strings.ToLower(linuxcap.GetProc().String())
|
||||
if strings.Contains(str, "cap_net_admin") || str == "=ep" {
|
||||
cap_net_admin = 1
|
||||
} else {
|
||||
cap_net_admin = -1
|
||||
}
|
||||
}
|
||||
|
||||
// check is in VPN mode
|
||||
if is_fwmark_exist(514) {
|
||||
if cap_net_admin == 1 {
|
||||
if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, 514); err != nil {
|
||||
log.Println("syscall.SetsockoptInt:", err)
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if err := cmsgProtect(int(fd), "./protect"); err != nil {
|
||||
log.Println("cmsgProtect:", err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func cmsgProtect(fd int, unixPath string) error {
|
||||
socket, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Close(socket)
|
||||
|
||||
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &syscall.Timeval{Sec: 3})
|
||||
syscall.SetsockoptTimeval(socket, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &syscall.Timeval{Sec: 3})
|
||||
|
||||
err = syscall.Connect(socket, &syscall.SockaddrUnix{Name: unixPath})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = syscall.Sendmsg(socket, nil, syscall.UnixRights(fd), nil, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dummy := []byte{1}
|
||||
n, err := syscall.Read(socket, dummy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != 1 {
|
||||
return fmt.Errorf("cmsgProtect protect failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func is_fwmark_exist(number int) bool {
|
||||
var err error
|
||||
|
||||
if rtnetlink_conn == nil {
|
||||
rtnetlink_conn, err = rtnetlink.Dial(nil)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
rules, err := rtnetlink_conn.Rule.List()
|
||||
if err != nil {
|
||||
rtnetlink_conn = nil
|
||||
return false
|
||||
}
|
||||
|
||||
for _, rule := range rules {
|
||||
if rule.Attributes != nil && rule.Attributes.FwMark != nil && uint32(number) == *rule.Attributes.FwMark {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func init() {
|
||||
protect.FdProtector = &fwmarkProtector{}
|
||||
}
|
||||
92
go/protect_server/protect_server_linux.go
Normal file
92
go/protect_server/protect_server_linux.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package protect_server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"reflect"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func getOneFd(socket int) (int, error) {
|
||||
// recvmsg
|
||||
buf := make([]byte, syscall.CmsgSpace(4))
|
||||
_, _, _, _, err := syscall.Recvmsg(socket, nil, buf, 0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// parse control msgs
|
||||
var msgs []syscall.SocketControlMessage
|
||||
msgs, err = syscall.ParseSocketControlMessage(buf)
|
||||
|
||||
if len(msgs) != 1 {
|
||||
return 0, fmt.Errorf("invaild msgs count: %d", len(msgs))
|
||||
}
|
||||
|
||||
var fds []int
|
||||
fds, err = syscall.ParseUnixRights(&msgs[0])
|
||||
if len(fds) != 1 {
|
||||
return 0, fmt.Errorf("invaild fds count: %d", len(fds))
|
||||
}
|
||||
return fds[0], nil
|
||||
}
|
||||
|
||||
// GetFdFromConn get net.Conn's file descriptor.
|
||||
func GetFdFromConn(l net.Conn) int {
|
||||
v := reflect.ValueOf(l)
|
||||
netFD := reflect.Indirect(reflect.Indirect(v).FieldByName("fd"))
|
||||
pfd := reflect.Indirect(netFD.FieldByName("pfd"))
|
||||
fd := int(pfd.FieldByName("Sysfd").Int())
|
||||
return fd
|
||||
}
|
||||
|
||||
func ServeProtect(path string, fwmark int) {
|
||||
os.Remove(path)
|
||||
defer os.Remove(path)
|
||||
|
||||
l, err := net.ListenUnix("unix", &net.UnixAddr{Name: path, Net: "unix"})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
os.Chmod(path, 0777)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
log.Println("Accept:", err)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
socket := GetFdFromConn(c)
|
||||
defer c.Close()
|
||||
|
||||
fd, err := getOneFd(socket)
|
||||
if err != nil {
|
||||
log.Println("getOneFd:", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_MARK, fwmark); err != nil {
|
||||
log.Println("syscall.SetsockoptInt:", err)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
c.Write([]byte{1})
|
||||
} else {
|
||||
c.Write([]byte{0})
|
||||
}
|
||||
}()
|
||||
}
|
||||
}()
|
||||
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-sigCh
|
||||
}
|
||||
9
go/protect_server/protect_server_other.go
Normal file
9
go/protect_server/protect_server_other.go
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build !linux
|
||||
|
||||
package protect_server
|
||||
|
||||
import "log"
|
||||
|
||||
func ServeProtect(path string, fwmark int) {
|
||||
log.Println("ServeProtect is not for this platform")
|
||||
}
|
||||
67
go/toolbox_linux.go
Normal file
67
go/toolbox_linux.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"nekoray_core/protect_server"
|
||||
"os"
|
||||
|
||||
"github.com/jsimonetti/rtnetlink"
|
||||
linuxcap "kernel.org/pub/linux/libs/security/libcap/cap"
|
||||
)
|
||||
|
||||
func ToolBox() {
|
||||
//
|
||||
var protectListenPath string
|
||||
var protectFwMark int
|
||||
//
|
||||
flag.StringVar(&protectListenPath, "protect-listen-path", "", "Set unix protect server listen path (Linux ROOT only)")
|
||||
flag.IntVar(&protectFwMark, "protect-fwmark", 0, "Set unix protect fwmark (Linux ROOT only)")
|
||||
flag.CommandLine.Parse(os.Args[3:])
|
||||
//
|
||||
switch os.Args[2] {
|
||||
case "rule":
|
||||
{
|
||||
// Dial a connection to the rtnetlink socket
|
||||
conn, err := rtnetlink.Dial(nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Request a list of rules
|
||||
rules, err := conn.Rule.List()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, rule := range rules {
|
||||
log.Printf("%+v", rule)
|
||||
log.Printf("%+v", rule.Attributes)
|
||||
}
|
||||
|
||||
for _, rule := range rules {
|
||||
if rule.Attributes.FwMark != nil {
|
||||
log.Printf("%+v", rule.Attributes)
|
||||
log.Println(*rule.Attributes.FwMark, *rule.Attributes.Table)
|
||||
}
|
||||
}
|
||||
}
|
||||
case "cap":
|
||||
{
|
||||
set := linuxcap.GetProc()
|
||||
if set != nil {
|
||||
log.Println(set)
|
||||
}
|
||||
}
|
||||
case "protect":
|
||||
{
|
||||
if protectListenPath == "" {
|
||||
log.Println("missing protect-listen-path")
|
||||
return
|
||||
}
|
||||
log.Println(protectListenPath, protectFwMark)
|
||||
protect_server.ServeProtect(protectListenPath, protectFwMark)
|
||||
}
|
||||
}
|
||||
}
|
||||
6
go/toolbox_other.go
Normal file
6
go/toolbox_other.go
Normal file
@@ -0,0 +1,6 @@
|
||||
//go:build !windows && !linux
|
||||
|
||||
package main
|
||||
|
||||
func ToolBox() {
|
||||
}
|
||||
26
go/toolbox_windows.go
Normal file
26
go/toolbox_windows.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
func ToolBox() {
|
||||
switch os.Args[2] {
|
||||
case "if":
|
||||
{
|
||||
intfs, err := net.Interfaces()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
for _, intf := range intfs {
|
||||
log.Println(intf)
|
||||
}
|
||||
for _, route := range routes {
|
||||
log.Println(route)
|
||||
}
|
||||
log.Println("Upstream:", getBindInterfaceIndex())
|
||||
}
|
||||
}
|
||||
}
|
||||
62
go/tun_linux.go
Normal file
62
go/tun_linux.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"libcore"
|
||||
"nekoray_core/gen"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
gvisorTun "gvisor.dev/gvisor/pkg/tcpip/link/tun"
|
||||
)
|
||||
|
||||
var tun2ray *libcore.Tun2ray
|
||||
var tun_fd int
|
||||
var tun_lock sync.Mutex
|
||||
|
||||
func TunStart(config *gen.SetTunReq) (err error) {
|
||||
tun_lock.Lock()
|
||||
defer tun_lock.Unlock()
|
||||
|
||||
if tun2ray != nil {
|
||||
return errors.New("tun aleary started")
|
||||
}
|
||||
|
||||
tun_fd, err = gvisorTun.Open(config.Name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
tun2ray, err = libcore.NewTun2ray(&libcore.TunConfig{
|
||||
FileDescriptor: int32(tun_fd),
|
||||
MTU: config.Mtu,
|
||||
V2Ray: instance, // use current if started
|
||||
Implementation: config.Implementation,
|
||||
Sniffing: true,
|
||||
FakeDNS: config.Fakedns,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func TunStop() {
|
||||
tun_lock.Lock()
|
||||
defer tun_lock.Unlock()
|
||||
|
||||
if tun2ray != nil {
|
||||
tun2ray.Close()
|
||||
tun2ray = nil
|
||||
if tun_fd > 0 {
|
||||
syscall.Close(tun_fd)
|
||||
}
|
||||
tun_fd = 0
|
||||
}
|
||||
}
|
||||
|
||||
func TunSetV2ray(i *libcore.V2RayInstance) {
|
||||
tun_lock.Lock()
|
||||
defer tun_lock.Unlock()
|
||||
|
||||
if tun2ray != nil {
|
||||
tun2ray.SetV2ray(i)
|
||||
}
|
||||
}
|
||||
19
go/tun_stub.go
Normal file
19
go/tun_stub.go
Normal file
@@ -0,0 +1,19 @@
|
||||
//go:build !linux
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"libcore"
|
||||
"nekoray_core/gen"
|
||||
)
|
||||
|
||||
func TunStart(config *gen.SetTunReq) error {
|
||||
return errors.New("not for this platform")
|
||||
}
|
||||
|
||||
func TunStop() {
|
||||
}
|
||||
|
||||
func TunSetV2ray(i *libcore.V2RayInstance) {
|
||||
}
|
||||
2
libs/.gitignore
vendored
Normal file
2
libs/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
deps
|
||||
downloaded
|
||||
6
libs/README
Normal file
6
libs/README
Normal file
@@ -0,0 +1,6 @@
|
||||
依赖
|
||||
libs/deps/*
|
||||
libs/deps/windows-x64 (vcpkg)
|
||||
libs/deps/built (prefix)
|
||||
|
||||
全部在项目根目录运行
|
||||
54
libs/build_deps_all.sh
Executable file
54
libs/build_deps_all.sh
Executable file
@@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
cd libs
|
||||
|
||||
# libs/deps/...
|
||||
mkdir -p deps; cd deps
|
||||
INSTLL_PREFIX=$PWD/built
|
||||
mkdir -p $INSTLL_PREFIX
|
||||
|
||||
#### yaml-cpp ####
|
||||
curl -L -o dl.zip https://github.com/jbeder/yaml-cpp/archive/refs/tags/yaml-cpp-0.7.0.zip
|
||||
unzip dl.zip
|
||||
|
||||
cd yaml-*
|
||||
mkdir -p build; cd build
|
||||
|
||||
cmake .. -GNinja -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$INSTLL_PREFIX
|
||||
ninja && ninja install
|
||||
|
||||
cd ../..
|
||||
|
||||
#### ZXing ####
|
||||
curl -L -o dl.zip https://github.com/nu-book/zxing-cpp/archive/refs/tags/v1.3.0.zip
|
||||
unzip dl.zip
|
||||
|
||||
cd zxing-*
|
||||
mkdir -p build; cd build
|
||||
|
||||
cmake .. -GNinja -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release -DBUILD_EXAMPLES=OFF -DBUILD_BLACKBOX_TESTS=OFF -DCMAKE_INSTALL_PREFIX=$INSTLL_PREFIX
|
||||
ninja && ninja install
|
||||
|
||||
cd ../..
|
||||
|
||||
#### protobuf ####
|
||||
git clone --recurse-submodules -b v21.4 --depth 1 --shallow-submodules https://github.com/protocolbuffers/protobuf
|
||||
|
||||
#备注:交叉编译要在 host 也安装 protobuf 并且版本一致,编译安装,同参数,安装到 /usr/local
|
||||
|
||||
mkdir -p protobuf/build
|
||||
cd protobuf/build
|
||||
|
||||
cmake .. -GNinja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SHARED_LIBS=OFF \
|
||||
-Dprotobuf_MSVC_STATIC_RUNTIME=OFF \
|
||||
-Dprotobuf_BUILD_TESTS=OFF \
|
||||
-DCMAKE_INSTALL_PREFIX=$INSTLL_PREFIX
|
||||
ninja && ninja install
|
||||
|
||||
cd ../..
|
||||
|
||||
#### clean ####
|
||||
rm -rf dl.zip yaml-* zxing-* protobuf
|
||||
46
libs/deploy_common.sh
Normal file
46
libs/deploy_common.sh
Normal file
@@ -0,0 +1,46 @@
|
||||
SRC_ROOT="$PWD"
|
||||
DEST="$PWD/deployment/nekoray"
|
||||
BUILD="$SRC_ROOT/build"
|
||||
|
||||
mkdir -p $DEST
|
||||
mkdir -p $BUILD
|
||||
|
||||
export CGO_ENABLED=0
|
||||
|
||||
#### Go: updater ####
|
||||
pushd updater
|
||||
go build -o $DEST -trimpath -ldflags "-w -s"
|
||||
popd
|
||||
|
||||
#### libcore ####
|
||||
COMMIT_M=$(cat matsuri_commit.txt)
|
||||
COMMIT_V=$(cat core_commit.txt)
|
||||
version_standalone="nekoray-"$(cat nekoray_version.txt)
|
||||
|
||||
pushd ..
|
||||
|
||||
git clone --no-checkout https://github.com/MatsuriDayo/Matsuri.git
|
||||
git clone --no-checkout https://github.com/MatsuriDayo/v2ray-core.git
|
||||
|
||||
pushd Matsuri
|
||||
git checkout $COMMIT_M
|
||||
popd
|
||||
|
||||
pushd v2ray-core
|
||||
git checkout $COMMIT_V
|
||||
version_v2ray=$(git log --pretty=format:'%h' -n 1)
|
||||
popd
|
||||
|
||||
popd
|
||||
|
||||
#### Go: nekoray_core ####
|
||||
pushd go
|
||||
go build -o $DEST -trimpath -ldflags "-w -s -X main.version_v2ray=$version_v2ray -X main.version_standalone=$version_standalone"
|
||||
popd
|
||||
|
||||
#### Download: geoip ####
|
||||
curl -Lso $DEST/geoip.dat "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/download/202206042210/geoip.dat"
|
||||
curl -Lso $DEST/geosite.dat "https://github.com/v2fly/domain-list-community/releases/download/20220604062951/dlc.dat"
|
||||
|
||||
#### copy assets ####
|
||||
cp assets/* $DEST
|
||||
24
libs/deploy_linux64.sh
Executable file
24
libs/deploy_linux64.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source libs/deploy_common.sh
|
||||
|
||||
#### updater to launcher ####
|
||||
mv $DEST/updater $DEST/launcher
|
||||
|
||||
#### copy binary ####
|
||||
cp $BUILD/nekoray $DEST
|
||||
|
||||
#### Download: prebuilt runtime ####
|
||||
curl -Lso usr.zip https://github.com/MatsuriDayo/nekoray_qt_runtime/releases/download/20220503/20220705-5.15.2-linux64.zip
|
||||
unzip usr.zip
|
||||
mv usr $DEST
|
||||
|
||||
#### copy runtime ####
|
||||
LIB=$SRC_ROOT/libs/deps/built/lib
|
||||
#cp $LIB/libZXing.so.1 $DEST/usr/lib
|
||||
|
||||
#### pack tar ####
|
||||
chmod +x $DEST/nekoray $DEST/nekoray_core $DEST/launcher
|
||||
tar cvzf $SRC_ROOT/deployment/$version_standalone-linux64.tar.gz -C $SRC_ROOT/deployment nekoray
|
||||
rm -rf $DEST $BUILD
|
||||
29
libs/deploy_windows64.sh
Executable file
29
libs/deploy_windows64.sh
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
source libs/deploy_common.sh
|
||||
|
||||
#### Go: sing-box ####
|
||||
pushd $BUILD
|
||||
curl -Lso sing-box.zip https://github.com/SagerNet/sing-box/archive/64dbac813837bbadfaeec1a6e0d064875a123e5e.zip
|
||||
unzip sing-box.zip
|
||||
pushd sing-box-*/cmd/sing-box
|
||||
go build -o $DEST -trimpath -ldflags "-w -s"
|
||||
popd
|
||||
popd
|
||||
|
||||
#### copy exe ####
|
||||
cp $BUILD/nekoray.exe $DEST
|
||||
|
||||
#### deploy qt & DLL runtime ####
|
||||
pushd $DEST
|
||||
windeployqt nekoray.exe --no-compiler-runtime --no-system-d3d-compiler --no-opengl-sw --verbose 2
|
||||
curl -LSsO https://github.com/MatsuriDayo/nekoray_qt_runtime/releases/download/20220503/libcrypto-1_1-x64.dll
|
||||
curl -LSsO https://github.com/MatsuriDayo/nekoray_qt_runtime/releases/download/20220503/libssl-1_1-x64.dll
|
||||
rm -rf translations
|
||||
popd
|
||||
|
||||
#### pack zip ####
|
||||
7z a $SRC_ROOT/deployment/$version_standalone-windows64.zip $DEST
|
||||
cp $BUILD/*.pdb $SRC_ROOT/deployment/
|
||||
rm -rf $DEST $BUILD
|
||||
21
libs/dl.sh
Executable file
21
libs/dl.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd libs
|
||||
|
||||
mkdir -p deps; cd ./deps
|
||||
mkdir -p downloaded; cd ./downloaded;
|
||||
|
||||
NAME=$1
|
||||
echo "Downloading: $NAME"
|
||||
curl -sL $2 -o $NAME;
|
||||
|
||||
cd ..
|
||||
|
||||
for f in $(ls ./downloaded)
|
||||
do
|
||||
7z x -y ./downloaded/$f
|
||||
done
|
||||
|
||||
# libs/deps/windows-x64/installed/
|
||||
|
||||
rm -rf downloaded
|
||||
28
main/Const.hpp
Normal file
28
main/Const.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
namespace NekoRay {
|
||||
namespace DomainMatcher {
|
||||
enum DomainMatcher {
|
||||
DEFAULT,
|
||||
MPH,
|
||||
};
|
||||
}
|
||||
|
||||
namespace SniffingMode {
|
||||
enum SniffingMode {
|
||||
DISABLE,
|
||||
FOR_ROUTING,
|
||||
FOR_DESTINATION,
|
||||
};
|
||||
}
|
||||
|
||||
namespace SystemProxyMode {
|
||||
enum SystemProxyMode {
|
||||
DISABLE,
|
||||
SYSTEM_PROXY,
|
||||
VPN,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
88
main/GuiUtils.hpp
Normal file
88
main/GuiUtils.hpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include <QMenu>
|
||||
#include <QWidget>
|
||||
|
||||
// Dialogs
|
||||
|
||||
inline QWidget *mainwindow;
|
||||
|
||||
#define Dialog_DialogBasicSettings "DialogBasicSettings"
|
||||
#define Dialog_DialogEditProfile "DialogEditProfile"
|
||||
#define Dialog_DialogManageGroups "DialogManageGroups"
|
||||
#define Dialog_DialogManageRoutes "DialogManageRoutes"
|
||||
|
||||
// Utils
|
||||
|
||||
inline QList<QAction *>
|
||||
CreateActions(QWidget *parent, const QList<QString> &texts, const std::function<void(QAction *)> &slot) {
|
||||
QList<QAction *> acts;
|
||||
|
||||
for (const auto &text: texts) {
|
||||
acts.push_back(new QAction(text, parent)); //按顺序来
|
||||
}
|
||||
|
||||
for (int i = 0; i < acts.size(); i++) {
|
||||
if (acts[i]->text() == "[Separator]") {
|
||||
acts[i]->setSeparator(true);
|
||||
acts[i]->setText("");
|
||||
acts[i]->setDisabled(true);
|
||||
acts[i]->setData(-1);
|
||||
} else {
|
||||
acts[i]->setData(i);
|
||||
QObject::connect(acts[i], &QAction::triggered, parent, [=] {
|
||||
slot(acts[i]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return acts;
|
||||
}
|
||||
|
||||
inline QMenu *CreateMenu(QWidget *parent, const QList<QString> &texts, const std::function<void(QAction *)> &slot) {
|
||||
auto menu = new QMenu(parent);
|
||||
menu->addActions(CreateActions(parent, texts, slot));
|
||||
return menu;
|
||||
}
|
||||
|
||||
#define QRegExpValidator_Number new QRegularExpressionValidator(QRegularExpression("^[0-9]+$")
|
||||
|
||||
// NekoRay Save&Load
|
||||
|
||||
#define P_LOAD_STRING(a) ui->a->setText(bean->a);
|
||||
#define P_SAVE_STRING(a) bean->a = ui->a->text();
|
||||
#define P_SAVE_STRING_QTEXTEDIT(a) bean->a = ui->a->toPlainText();
|
||||
#define D_LOAD_STRING(a) ui->a->setText(NekoRay::dataStore->a);
|
||||
#define D_SAVE_STRING(a) NekoRay::dataStore->a = ui->a->text();
|
||||
#define P_C_LOAD_STRING(a) CACHE.a = bean->a;
|
||||
#define P_C_SAVE_STRING(a) bean->a = CACHE.a;
|
||||
#define D_C_LOAD_STRING(a) CACHE.a = NekoRay::dataStore->a;
|
||||
#define D_C_SAVE_STRING(a) NekoRay::dataStore->a = CACHE.a;
|
||||
#define P_LOAD_INT(a) ui->a->setText(Int2String(bean->a)); ui->a->setValidator(QRegExpValidator_Number, this));
|
||||
#define P_SAVE_INT(a) bean->a = ui->a->text().toInt();
|
||||
#define D_LOAD_INT(a) ui->a->setText(Int2String(NekoRay::dataStore->a)); ui->a->setValidator(QRegExpValidator_Number, this));
|
||||
#define D_SAVE_INT(a) NekoRay::dataStore->a = ui->a->text().toInt();
|
||||
#define P_LOAD_COMBO(a) ui->a->setCurrentText(bean->a);
|
||||
#define P_SAVE_COMBO(a) bean->a = ui->a->currentText();
|
||||
|
||||
#define D_LOAD_INT_ENABLE(i, e) \
|
||||
if (NekoRay::dataStore->i > 0) { \
|
||||
ui->e->setChecked(true); \
|
||||
ui->i->setText(Int2String(NekoRay::dataStore->i)); \
|
||||
} else { \
|
||||
ui->e->setChecked(false); \
|
||||
ui->i->setText(Int2String(-NekoRay::dataStore->i)); \
|
||||
} \
|
||||
ui->i->setValidator(QRegExpValidator_Number, this));
|
||||
#define D_SAVE_INT_ENABLE(i, e) \
|
||||
if (ui->e->isChecked()) { \
|
||||
NekoRay::dataStore->i = ui->i->text().toInt(); \
|
||||
} else { \
|
||||
NekoRay::dataStore->i = -ui->i->text().toInt(); \
|
||||
}
|
||||
|
||||
#define C_EDIT_JSON_ALLOW_EMPTY(a) auto editor = new JsonEditor(QString2QJsonObject(CACHE.a), this); \
|
||||
auto result = editor->OpenEditor(); \
|
||||
CACHE.a = QJsonObject2QString(result, true); \
|
||||
if (result.isEmpty()) CACHE.a = ""; \
|
||||
editor->deleteLater();
|
||||
320
main/NekoRay.cpp
Normal file
320
main/NekoRay.cpp
Normal file
@@ -0,0 +1,320 @@
|
||||
#include "NekoRay.hpp"
|
||||
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
|
||||
namespace NekoRay {
|
||||
|
||||
DataStore *dataStore = new DataStore();
|
||||
|
||||
// datastore
|
||||
|
||||
DataStore::DataStore() : JsonStore("groups/nekoray.json") {
|
||||
_add(new configItem("extraCore", dynamic_cast<JsonStore *>(extraCore), itemType::jsonStore));
|
||||
|
||||
_add(new configItem("core_path", &core_path, itemType::string));
|
||||
_add(new configItem("user_agent", &user_agent, itemType::string));
|
||||
_add(new configItem("test_url", &test_url, itemType::string));
|
||||
_add(new configItem("current_group", ¤t_group, itemType::integer));
|
||||
_add(new configItem("inbound_address", &inbound_address, itemType::string));
|
||||
_add(new configItem("inbound_socks_port", &inbound_socks_port, itemType::integer));
|
||||
_add(new configItem("inbound_http_port", &inbound_http_port, itemType::integer));
|
||||
_add(new configItem("log_level", &log_level, itemType::string));
|
||||
_add(new configItem("remote_dns", &remote_dns, itemType::string));
|
||||
_add(new configItem("direct_dns", &direct_dns, itemType::string));
|
||||
_add(new configItem("domain_matcher", &domain_matcher, itemType::integer));
|
||||
_add(new configItem("domain_strategy", &domain_strategy, itemType::string));
|
||||
_add(new configItem("outbound_domain_strategy", &outbound_domain_strategy, itemType::string));
|
||||
_add(new configItem("sniffing_mode", &sniffing_mode, itemType::integer));
|
||||
_add(new configItem("mux_cool", &mux_cool, itemType::integer));
|
||||
_add(new configItem("traffic_loop_interval", &traffic_loop_interval, itemType::integer));
|
||||
_add(new configItem("dns_routing", &dns_routing, itemType::boolean));
|
||||
_add(new configItem("test_concurrent", &test_concurrent, itemType::integer));
|
||||
_add(new configItem("theme", &theme, itemType::string));
|
||||
_add(new configItem("custom_inbound", &custom_inbound, itemType::string));
|
||||
_add(new configItem("custom_route", &custom_route_global, itemType::string));
|
||||
_add(new configItem("v2ray_asset_dir", &v2ray_asset_dir, itemType::string));
|
||||
_add(new configItem("sub_use_proxy", &sub_use_proxy, itemType::boolean));
|
||||
_add(new configItem("enhance_domain", &enhance_resolve_server_domain, itemType::boolean));
|
||||
_add(new configItem("remember_id", &remember_id, itemType::integer));
|
||||
_add(new configItem("remember_enable", &remember_enable, itemType::boolean));
|
||||
_add(new configItem("start_minimal", &start_minimal, itemType::boolean));
|
||||
_add(new configItem("language", &language, itemType::integer));
|
||||
_add(new configItem("spmode", &system_proxy_mode, itemType::integer));
|
||||
_add(new configItem("insecure_hint", &insecure_hint, itemType::boolean));
|
||||
_add(new configItem("skip_cert", &skip_cert, itemType::boolean));
|
||||
_add(new configItem("hk_mw", &hotkey_mainwindow, itemType::string));
|
||||
_add(new configItem("hk_group", &hotkey_group, itemType::string));
|
||||
_add(new configItem("hk_route", &hotkey_route, itemType::string));
|
||||
_add(new configItem("fakedns", &fake_dns, itemType::boolean));
|
||||
_add(new configItem("active_routing", &active_routing, itemType::string));
|
||||
_add(new configItem("mw_size", &mw_size, itemType::string));
|
||||
_add(new configItem("conn_stat", &connection_statistics, itemType::boolean));
|
||||
}
|
||||
|
||||
void DataStore::UpdateStartedId(int id) {
|
||||
started_id = id;
|
||||
if (remember_enable) {
|
||||
remember_id = id;
|
||||
Save();
|
||||
} else if (remember_id >= 0) {
|
||||
remember_id = -1919;
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
// preset routing
|
||||
Routing::Routing(int preset) : JsonStore() {
|
||||
if (preset == 1) {
|
||||
direct_ip = "geoip:cn\n"
|
||||
"geoip:private";
|
||||
direct_domain = "geosite:cn";
|
||||
proxy_ip = "";
|
||||
proxy_domain = "";
|
||||
block_ip = "";
|
||||
block_domain = "geosite:category-ads-all\n"
|
||||
"domain:appcenter.ms\n"
|
||||
"domain:app-measurement.com\n"
|
||||
"domain:firebase.io\n"
|
||||
"domain:crashlytics.com\n"
|
||||
"domain:google-analytics.com";
|
||||
}
|
||||
_add(new configItem("direct_ip", &this->direct_ip, itemType::string));
|
||||
_add(new configItem("direct_domain", &this->direct_domain, itemType::string));
|
||||
_add(new configItem("proxy_ip", &this->proxy_ip, itemType::string));
|
||||
_add(new configItem("proxy_domain", &this->proxy_domain, itemType::string));
|
||||
_add(new configItem("block_ip", &this->block_ip, itemType::string));
|
||||
_add(new configItem("block_domain", &this->block_domain, itemType::string));
|
||||
_add(new configItem("custom", &this->custom, itemType::string));
|
||||
}
|
||||
|
||||
QString Routing::toString() const {
|
||||
return QString("[Proxy] %1\n[Proxy] %2\n[Direct] %3\n[Direct] %4\n[Block] %5\n[Block] %6")
|
||||
.arg(SplitLines(proxy_domain).join(","))
|
||||
.arg(SplitLines(proxy_ip).join(","))
|
||||
.arg(SplitLines(direct_domain).join(","))
|
||||
.arg(SplitLines(direct_ip).join(","))
|
||||
.arg(SplitLines(block_domain).join(","))
|
||||
.arg(SplitLines(block_ip).join(","));
|
||||
}
|
||||
|
||||
QStringList Routing::List() {
|
||||
QStringList l;
|
||||
QDir d;
|
||||
if (d.exists("routes")) {
|
||||
QDir dr("routes");
|
||||
return dr.entryList(QDir::Files);
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
void Routing::SetToActive(const QString &name) {
|
||||
dataStore->routing->fn = "routes/" + name;
|
||||
dataStore->routing->Load();
|
||||
dataStore->active_routing = name;
|
||||
dataStore->Save();
|
||||
}
|
||||
|
||||
// NO default extra core
|
||||
|
||||
ExtraCore::ExtraCore() : JsonStore() {
|
||||
_add(new configItem("core_map", &this->core_map, itemType::string));
|
||||
}
|
||||
|
||||
QString ExtraCore::Get(const QString &id) const {
|
||||
auto obj = QString2QJsonObject(core_map);
|
||||
for (const auto &c: obj.keys()) {
|
||||
if (c == id) return obj[id].toString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void ExtraCore::Set(const QString &id, const QString &path) {
|
||||
auto obj = QString2QJsonObject(core_map);
|
||||
obj[id] = path;
|
||||
core_map = QJsonObject2QString(obj, true);
|
||||
}
|
||||
|
||||
void ExtraCore::Delete(const QString &id) {
|
||||
auto obj = QString2QJsonObject(core_map);
|
||||
obj.remove(id);
|
||||
core_map = QJsonObject2QString(obj, true);
|
||||
}
|
||||
|
||||
// 添加关联
|
||||
void JsonStore::_add(configItem *item) {
|
||||
_map.insert(item->name, QSharedPointer<configItem>(item));
|
||||
}
|
||||
|
||||
QSharedPointer<configItem> JsonStore::_get(const QString &name) {
|
||||
// 直接 [] 会设置一个 nullptr ,所以先判断是否存在
|
||||
if (_map.contains(name)) {
|
||||
return _map[name];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QJsonObject JsonStore::ToJson() {
|
||||
QJsonObject object;
|
||||
for (const auto &_item: _map) {
|
||||
auto item = _item.get();
|
||||
switch (item->type) {
|
||||
case itemType::string:
|
||||
object.insert(item->name, *(QString *) item->ptr);
|
||||
break;
|
||||
case itemType::integer:
|
||||
object.insert(item->name, *(int *) item->ptr);
|
||||
break;
|
||||
case itemType::integer64:
|
||||
object.insert(item->name, *(long long *) item->ptr);
|
||||
break;
|
||||
case itemType::boolean:
|
||||
object.insert(item->name, *(bool *) item->ptr);
|
||||
break;
|
||||
case itemType::stringList:
|
||||
object.insert(item->name, QList2QJsonArray<QString>(*(QList<QString> *) item->ptr));
|
||||
break;
|
||||
case itemType::integerList:
|
||||
object.insert(item->name, QList2QJsonArray<int>(*(QList<int> *) item->ptr));
|
||||
break;
|
||||
case itemType::jsonStore:
|
||||
// _add 时应关联对应 JsonStore 的指针
|
||||
object.insert(item->name, ((JsonStore *) item->ptr)->ToJson());
|
||||
break;
|
||||
}
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
QByteArray JsonStore::ToJsonBytes() {
|
||||
QJsonDocument document;
|
||||
document.setObject(ToJson());
|
||||
return document.toJson(save_control_compact ? QJsonDocument::Compact : QJsonDocument::Indented);
|
||||
}
|
||||
|
||||
void JsonStore::FromJson(QJsonObject object) {
|
||||
for (const auto &key: object.keys()) {
|
||||
if (_map.count(key) == 0) {
|
||||
if (debug_verbose) {
|
||||
qDebug() << QString("unknown key\n%1\n%2").arg(key, QJsonObject2QString(object, false));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
auto value = object[key];
|
||||
auto item = _map[key].get();
|
||||
|
||||
if (item == nullptr)
|
||||
continue; // 故意忽略
|
||||
|
||||
// 根据类型修改ptr的内容
|
||||
switch (item->type) {
|
||||
case itemType::string:
|
||||
if (value.type() != QJsonValue::String) {
|
||||
MessageBoxWarning("错误", "Not a string\n" + key);
|
||||
continue;
|
||||
}
|
||||
*(QString *) item->ptr = value.toString();
|
||||
break;
|
||||
case itemType::integer:
|
||||
if (value.type() != QJsonValue::Double) {
|
||||
MessageBoxWarning("错误", "Not a int\n" + key);
|
||||
continue;
|
||||
}
|
||||
*(int *) item->ptr = value.toInt();
|
||||
break;
|
||||
case itemType::integer64:
|
||||
if (value.type() != QJsonValue::Double) {
|
||||
MessageBoxWarning("错误", "Not a int64\n" + key);
|
||||
continue;
|
||||
}
|
||||
*(long long *) item->ptr = value.toDouble();
|
||||
break;
|
||||
case itemType::boolean:
|
||||
if (value.type() != QJsonValue::Bool) {
|
||||
MessageBoxWarning("错误", "Not a bool\n" + key);
|
||||
continue;
|
||||
}
|
||||
*(bool *) item->ptr = value.toBool();
|
||||
break;
|
||||
case itemType::stringList:
|
||||
if (value.type() != QJsonValue::Array) {
|
||||
MessageBoxWarning("错误", "Not a Array\n" + key);
|
||||
continue;
|
||||
}
|
||||
*(QList<QString> *) item->ptr = QJsonArray2QListString(value.toArray());
|
||||
break;
|
||||
case itemType::integerList:
|
||||
if (value.type() != QJsonValue::Array) {
|
||||
MessageBoxWarning("错误", "Not a Array\n" + key);
|
||||
continue;
|
||||
}
|
||||
*(QList<int> *) item->ptr = QJsonArray2QListInt(value.toArray());
|
||||
break;
|
||||
case itemType::jsonStore:
|
||||
if (value.type() != QJsonValue::Object) {
|
||||
MessageBoxWarning("错误", "Not a json object\n" + key);
|
||||
continue;
|
||||
}
|
||||
if (load_control_no_jsonStore)
|
||||
continue;
|
||||
((JsonStore *) item->ptr)->FromJson(value.toObject());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &hook: _hooks_after_load) {
|
||||
hook();
|
||||
}
|
||||
}
|
||||
|
||||
void JsonStore::FromJsonBytes(const QByteArray &data) {
|
||||
QJsonParseError error{};
|
||||
auto document = QJsonDocument::fromJson(data, &error);
|
||||
|
||||
if (error.error != error.NoError) {
|
||||
if (debug_verbose) qDebug() << "QJsonParseError" << error.errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
FromJson(document.object());
|
||||
}
|
||||
|
||||
bool JsonStore::Save() {
|
||||
for (const auto &hook: _hooks_before_save) {
|
||||
hook();
|
||||
}
|
||||
|
||||
auto save_content = ToJsonBytes();
|
||||
auto changed = last_save_content != save_content;
|
||||
last_save_content = save_content;
|
||||
|
||||
QFile file;
|
||||
file.setFileName(fn);
|
||||
file.open(QIODevice::ReadWrite | QIODevice::Truncate);
|
||||
file.write(save_content);
|
||||
file.close();
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool JsonStore::Load() {
|
||||
QFile file;
|
||||
file.setFileName(fn);
|
||||
|
||||
if (!file.exists() && !load_control_force)
|
||||
return false;
|
||||
|
||||
bool ok = file.open(QIODevice::ReadOnly);
|
||||
if (!ok) {
|
||||
MessageBoxWarning("error", "can not open config " + fn + "\n" + file.errorString());
|
||||
} else {
|
||||
last_save_content = file.readAll();
|
||||
FromJsonBytes(last_save_content);
|
||||
}
|
||||
|
||||
file.close();
|
||||
return ok;
|
||||
}
|
||||
|
||||
}
|
||||
6
main/NekoRay.hpp
Normal file
6
main/NekoRay.hpp
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "Const.hpp"
|
||||
#include "NekoRay_Utils.hpp"
|
||||
#include "NekoRay_ConfigItem.hpp"
|
||||
#include "NekoRay_DataStore.hpp"
|
||||
63
main/NekoRay_ConfigItem.hpp
Normal file
63
main/NekoRay_ConfigItem.hpp
Normal file
@@ -0,0 +1,63 @@
|
||||
// DO NOT INCLUDE THIS
|
||||
|
||||
namespace NekoRay {
|
||||
// config 工具
|
||||
enum itemType {
|
||||
string,
|
||||
integer,
|
||||
integer64,
|
||||
boolean,
|
||||
stringList,
|
||||
integerList,
|
||||
jsonStore,
|
||||
};
|
||||
|
||||
class configItem {
|
||||
public:
|
||||
QString name;
|
||||
void *ptr;
|
||||
itemType type;
|
||||
|
||||
configItem(QString n, void *p, itemType t) {
|
||||
name = std::move(n);
|
||||
ptr = p;
|
||||
type = t;
|
||||
}
|
||||
};
|
||||
|
||||
// 可格式化对象
|
||||
class JsonStore {
|
||||
public:
|
||||
QMap<QString, QSharedPointer<configItem>> _map;
|
||||
QList<std::function<void()>> _hooks_after_load;
|
||||
QList<std::function<void()>> _hooks_before_save;
|
||||
QString fn;
|
||||
bool debug_verbose = false;
|
||||
bool load_control_force = false;
|
||||
bool load_control_no_jsonStore = false; //不加载 json object
|
||||
bool save_control_compact = false;
|
||||
QByteArray last_save_content;
|
||||
|
||||
JsonStore() = default;
|
||||
|
||||
explicit JsonStore(QString fileName) {
|
||||
fn = std::move(fileName);
|
||||
}
|
||||
|
||||
void _add(configItem *item);
|
||||
|
||||
QSharedPointer<configItem> _get(const QString &name);
|
||||
|
||||
QJsonObject ToJson();
|
||||
|
||||
QByteArray ToJsonBytes();
|
||||
|
||||
void FromJson(QJsonObject object);
|
||||
|
||||
void FromJsonBytes(const QByteArray &data);
|
||||
|
||||
bool Save();
|
||||
|
||||
bool Load();
|
||||
};
|
||||
}
|
||||
122
main/NekoRay_DataStore.hpp
Normal file
122
main/NekoRay_DataStore.hpp
Normal file
@@ -0,0 +1,122 @@
|
||||
// DO NOT INCLUDE THIS
|
||||
|
||||
namespace NekoRay {
|
||||
|
||||
class Routing : public JsonStore {
|
||||
public:
|
||||
QString direct_ip;
|
||||
QString direct_domain;
|
||||
QString proxy_ip;
|
||||
QString proxy_domain;
|
||||
QString block_ip;
|
||||
QString block_domain;
|
||||
QString custom = "{\"rules\": []}";
|
||||
|
||||
explicit Routing(int preset = 0);
|
||||
|
||||
QString toString() const;
|
||||
|
||||
static QStringList List();
|
||||
|
||||
static void SetToActive(const QString &name);
|
||||
};
|
||||
|
||||
class ExtraCore : public JsonStore {
|
||||
public:
|
||||
QString core_map;
|
||||
|
||||
explicit ExtraCore();
|
||||
|
||||
[[nodiscard]] QString Get(const QString &id) const;
|
||||
|
||||
void Set(const QString &id, const QString &path);
|
||||
|
||||
void Delete(const QString &id);
|
||||
};
|
||||
|
||||
class DataStore : public JsonStore {
|
||||
public:
|
||||
// Running
|
||||
|
||||
QString core_token;
|
||||
int core_port = 19810;
|
||||
int started_id = -1919;
|
||||
|
||||
// Saved
|
||||
|
||||
// Misc
|
||||
QString core_path = "../nekoray_core";
|
||||
QString log_level = "warning";
|
||||
QString user_agent = "ClashForAndroid/2.5.9.premium";
|
||||
bool sub_use_proxy = false;
|
||||
QString test_url = "http://cp.cloudflare.com/";
|
||||
int test_concurrent = 5;
|
||||
int traffic_loop_interval = 500;
|
||||
bool connection_statistics = false;
|
||||
int current_group = 0; //group id
|
||||
int mux_cool = -8;
|
||||
QString theme = "0";
|
||||
QString v2ray_asset_dir = "";
|
||||
int language = 0;
|
||||
QString mw_size = "";
|
||||
|
||||
// Security
|
||||
bool insecure_hint = true;
|
||||
bool skip_cert = false;
|
||||
|
||||
// Remember
|
||||
int system_proxy_mode = NekoRay::SystemProxyMode::DISABLE;
|
||||
int remember_id = -1919;
|
||||
bool remember_enable = false;
|
||||
bool start_minimal = false;
|
||||
|
||||
// Socks & HTTP Inbound
|
||||
QString inbound_address = "127.0.0.1";
|
||||
int inbound_socks_port = 2080;
|
||||
int inbound_http_port = -2081;
|
||||
QString custom_inbound = "{\"inbounds\": []}";
|
||||
|
||||
// DNS
|
||||
QString remote_dns = "https://8.8.8.8/dns-query";
|
||||
QString direct_dns = "https+local://223.5.5.5/dns-query";
|
||||
bool dns_routing = true;
|
||||
bool enhance_resolve_server_domain = false;
|
||||
|
||||
// Routing
|
||||
bool fake_dns = false;
|
||||
QString domain_strategy = "AsIs";
|
||||
QString outbound_domain_strategy = "AsIs";
|
||||
int sniffing_mode = SniffingMode::FOR_ROUTING;
|
||||
int domain_matcher = DomainMatcher::MPH;
|
||||
QString custom_route_global = "{\"rules\": []}";
|
||||
QString active_routing = "Default";
|
||||
|
||||
// Hotkey
|
||||
QString hotkey_mainwindow = "";
|
||||
QString hotkey_group = "";
|
||||
QString hotkey_route = "";
|
||||
|
||||
// Other Core
|
||||
ExtraCore *extraCore = new ExtraCore;
|
||||
|
||||
// Running Cache
|
||||
|
||||
Routing *routing = new Routing;
|
||||
int imported_count = 0;
|
||||
bool refreshing_group_list = false;
|
||||
|
||||
// Running Flags
|
||||
|
||||
bool flag_use_appdata = false;
|
||||
bool flag_many = false;
|
||||
|
||||
//
|
||||
|
||||
DataStore();
|
||||
|
||||
void UpdateStartedId(int id);
|
||||
};
|
||||
|
||||
extern DataStore *dataStore;
|
||||
|
||||
}
|
||||
103
main/NekoRay_Utils.cpp
Normal file
103
main/NekoRay_Utils.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#include "NekoRay_Utils.hpp"
|
||||
|
||||
#include "3rdparty/QThreadCreateThread.hpp"
|
||||
#include "main/GuiUtils.hpp"
|
||||
|
||||
#include <random>
|
||||
|
||||
#include <QUrlQuery>
|
||||
#include <QTcpServer>
|
||||
#include <QTimer>
|
||||
#include <QMessageBox>
|
||||
#include <QFile>
|
||||
|
||||
QString GetQueryValue(const QUrlQuery &q, const QString &key, const QString &def) {
|
||||
auto a = q.queryItemValue(key);
|
||||
if (a.isEmpty()) {
|
||||
return def;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
QString GetRandomString(int randomStringLength) {
|
||||
std::random_device rd;
|
||||
std::mt19937 mt(rd());
|
||||
|
||||
const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
|
||||
|
||||
std::uniform_int_distribution<int> dist(0, possibleCharacters.count() - 1);
|
||||
|
||||
QString randomString;
|
||||
for (int i = 0; i < randomStringLength; ++i) {
|
||||
QChar nextChar = possibleCharacters.at(dist(mt));
|
||||
randomString.append(nextChar);
|
||||
}
|
||||
return randomString;
|
||||
}
|
||||
|
||||
QByteArray ReadFile(const QString &path) {
|
||||
QFile file(path);
|
||||
file.open(QFile::ReadOnly);
|
||||
return file.readAll();
|
||||
}
|
||||
|
||||
QString ReadFileText(const QString &path) {
|
||||
QFile file(path);
|
||||
file.open(QFile::ReadOnly | QFile::Text);
|
||||
QTextStream stream(&file);
|
||||
return stream.readAll();
|
||||
}
|
||||
|
||||
int MkPort() {
|
||||
QTcpServer s;
|
||||
s.listen();
|
||||
auto port = s.serverPort();
|
||||
s.close();
|
||||
return port;
|
||||
}
|
||||
|
||||
bool IsIpAddress(const QString &str) {
|
||||
auto address = QHostAddress(str);
|
||||
if (address.protocol() == QAbstractSocket::IPv4Protocol || address.protocol() == QAbstractSocket::IPv6Protocol)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsIpAddressV4(const QString &str) {
|
||||
auto address = QHostAddress(str);
|
||||
if (address.protocol() == QAbstractSocket::IPv4Protocol)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsIpAddressV6(const QString &str) {
|
||||
auto address = QHostAddress(str);
|
||||
if (address.protocol() == QAbstractSocket::IPv6Protocol)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
int MessageBoxWarning(const QString &title, const QString &text) {
|
||||
return QMessageBox::warning(nullptr, title, text);
|
||||
}
|
||||
|
||||
int MessageBoxInfo(const QString &title, const QString &text) {
|
||||
return QMessageBox::information(nullptr, title, text);
|
||||
}
|
||||
|
||||
void runOnUiThread(const std::function<void()> &callback, QObject *parent) {
|
||||
// any thread
|
||||
auto *timer = new QTimer();
|
||||
timer->moveToThread(parent == nullptr ? mainwindow->thread() : parent->thread());
|
||||
timer->setSingleShot(true);
|
||||
QObject::connect(timer, &QTimer::timeout, [=]() {
|
||||
// main thread
|
||||
callback();
|
||||
timer->deleteLater();
|
||||
});
|
||||
QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, 0));
|
||||
}
|
||||
|
||||
void runOnNewThread(const std::function<void()> &callback) {
|
||||
createQThread(callback)->start();
|
||||
}
|
||||
180
main/NekoRay_Utils.hpp
Normal file
180
main/NekoRay_Utils.hpp
Normal file
@@ -0,0 +1,180 @@
|
||||
// DO NOT INCLUDE THIS
|
||||
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
|
||||
// Dialogs
|
||||
|
||||
inline std::function<void(QString)> showLog;
|
||||
inline std::function<void(QString, QString)> showLog_ext;
|
||||
inline std::function<void(QString)> showLog_ext_vt100;
|
||||
inline std::function<void(QString, QString)> dialog_message;
|
||||
|
||||
// Utils
|
||||
|
||||
#define QJSONARRAY_ADD(arr, add) for(const auto &a: (add)) { (arr) += a; }
|
||||
|
||||
inline QString SubStrBefore(QString str, const QString &sub) {
|
||||
if (!str.contains(sub)) return str;
|
||||
return str.left(str.indexOf(sub));
|
||||
}
|
||||
|
||||
inline QString SubStrAfter(QString str, const QString &sub) {
|
||||
if (!str.contains(sub)) return str;
|
||||
return str.right(str.length() - str.indexOf(sub) - sub.length());
|
||||
}
|
||||
|
||||
inline QString
|
||||
DecodeB64IfValid(const QString &input, QByteArray::Base64Option options = QByteArray::Base64Option::Base64Encoding) {
|
||||
auto result = QByteArray::fromBase64Encoding(input.toUtf8(),
|
||||
options | QByteArray::Base64Option::AbortOnBase64DecodingErrors);
|
||||
if (result) {
|
||||
return result.decoded;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
#define GetQuery(url) QUrlQuery((url).query(QUrl::ComponentFormattingOption::FullyDecoded));
|
||||
|
||||
QString GetQueryValue(const QUrlQuery &q, const QString &key, const QString &def = "");
|
||||
|
||||
inline QString Int2String(int i) {
|
||||
return QVariant(i).toString();
|
||||
}
|
||||
|
||||
inline QString Int2String(qint64 i) {
|
||||
return QVariant(i).toString();
|
||||
}
|
||||
|
||||
QString GetRandomString(int randomStringLength);
|
||||
|
||||
// QString >> QJson
|
||||
inline QJsonObject QString2QJsonObject(const QString &jsonString) {
|
||||
QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonString.toUtf8());
|
||||
QJsonObject jsonObject = jsonDocument.object();
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
// QJson >> QString
|
||||
inline QString QJsonObject2QString(const QJsonObject &jsonObject, bool compact) {
|
||||
return QString(QJsonDocument(jsonObject).toJson(compact ? QJsonDocument::Compact : QJsonDocument::Indented));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline QJsonArray QList2QJsonArray(const QList<T> &list) {
|
||||
QVariantList list2;
|
||||
for (auto &item: list)
|
||||
list2.append(item);
|
||||
return QJsonArray::fromVariantList(list2);
|
||||
}
|
||||
|
||||
inline QList<int> QJsonArray2QListInt(const QJsonArray &arr) {
|
||||
QList<int> list2;
|
||||
for (auto item: arr)
|
||||
list2.append(item.toInt());
|
||||
return list2;
|
||||
}
|
||||
|
||||
inline QList<QString> QJsonArray2QListString(const QJsonArray &arr) {
|
||||
QList<QString> list2;
|
||||
for (auto item: arr)
|
||||
list2.append(item.toString());
|
||||
return list2;
|
||||
}
|
||||
|
||||
inline QString UrlSafe_encode(const QString &s) {
|
||||
return s.toUtf8().toPercentEncoding().replace(" ", "%20");
|
||||
}
|
||||
|
||||
inline bool InRange(unsigned x, unsigned low, unsigned high) {
|
||||
return (low <= x && x <= high);
|
||||
}
|
||||
|
||||
inline QStringList SplitLines(const QString &_string) {
|
||||
return _string.split(QRegularExpression("[\r\n]"), Qt::SplitBehaviorFlags::SkipEmptyParts);
|
||||
}
|
||||
|
||||
QByteArray ReadFile(const QString &path);
|
||||
|
||||
QString ReadFileText(const QString &path);
|
||||
|
||||
// Net
|
||||
|
||||
int MkPort();
|
||||
|
||||
// Validators
|
||||
|
||||
bool IsIpAddress(const QString &str);
|
||||
|
||||
bool IsIpAddressV4(const QString &str);
|
||||
|
||||
bool IsIpAddressV6(const QString &str);
|
||||
|
||||
// [2001:4860:4860::8888] -> 2001:4860:4860::8888
|
||||
inline QString UnwrapIPV6Host(QString &str) {
|
||||
return str.replace("[", "").replace("]", "");
|
||||
}
|
||||
|
||||
// [2001:4860:4860::8888] or 2001:4860:4860::8888 -> [2001:4860:4860::8888]
|
||||
inline QString WrapIPV6Host(QString &str) {
|
||||
if (!IsIpAddressV6(str)) return str;
|
||||
return "[" + UnwrapIPV6Host(str) + "]";
|
||||
}
|
||||
|
||||
inline QString DisplayAddress(QString serverAddress, int serverPort) {
|
||||
return WrapIPV6Host(serverAddress) + ":" + Int2String(serverPort);
|
||||
};
|
||||
|
||||
// Format
|
||||
|
||||
inline QString DisplayTime(long long time, QLocale::FormatType formatType = QLocale::LongFormat) {
|
||||
QDateTime t;
|
||||
t.setSecsSinceEpoch(time);
|
||||
return QLocale().toString(t, formatType);
|
||||
}
|
||||
|
||||
inline QString ReadableSize(const qint64 &size) {
|
||||
double sizeAsDouble = size;
|
||||
static QStringList measures;
|
||||
if (measures.isEmpty())
|
||||
measures << "B"
|
||||
<< "KiB"
|
||||
<< "MiB"
|
||||
<< "GiB"
|
||||
<< "TiB"
|
||||
<< "PiB"
|
||||
<< "EiB"
|
||||
<< "ZiB"
|
||||
<< "YiB";
|
||||
QStringListIterator it(measures);
|
||||
QString measure(it.next());
|
||||
while (sizeAsDouble >= 1024.0 && it.hasNext()) {
|
||||
measure = it.next();
|
||||
sizeAsDouble /= 1024.0;
|
||||
}
|
||||
return QString::fromLatin1("%1 %2").arg(sizeAsDouble, 0, 'f', 2).arg(measure);
|
||||
}
|
||||
|
||||
// UI
|
||||
|
||||
int MessageBoxWarning(const QString &title, const QString &text);
|
||||
|
||||
int MessageBoxInfo(const QString &title, const QString &text);
|
||||
|
||||
void runOnUiThread(const std::function<void()> &callback, QObject *parent = nullptr);
|
||||
|
||||
void runOnNewThread(const std::function<void()> &callback);
|
||||
|
||||
template<typename EMITTER, typename SIGNAL, typename RECEIVER, typename ReceiverFunc>
|
||||
inline void connectOnce(EMITTER *emitter, SIGNAL signal, RECEIVER *receiver, ReceiverFunc f,
|
||||
Qt::ConnectionType connectionType = Qt::AutoConnection) {
|
||||
auto connection = std::make_shared<QMetaObject::Connection>();
|
||||
auto onTriggered = [connection, f](auto... arguments) {
|
||||
std::invoke(f, arguments...);
|
||||
QObject::disconnect(*connection);
|
||||
};
|
||||
|
||||
*connection = QObject::connect(emitter, signal, receiver, onTriggered, connectionType);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user