upload code

This commit is contained in:
arm64v8a
2022-05-03 19:25:17 +08:00
parent 6f7e9ef9ad
commit 77d354874e
275 changed files with 25135 additions and 0 deletions

146
.github/workflows/build-qv2ray-cmake.yml vendored Normal file
View 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
View 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
View File

@@ -0,0 +1,3 @@
[submodule "3rdparty/QHotkey"]
path = 3rdparty/QHotkey
url = https://github.com/Skycoder42/QHotkey.git

1
3rdparty/QHotkey vendored Submodule

Submodule 3rdparty/QHotkey added at 52e25acf22

38
3rdparty/QThreadCreateThread.hpp vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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 &parameters,
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 &parameters, const QString &workingDir,
bool aWait) {
return runProcessElevated(path, QStringList() << parameters, workingDir, aWait);
}

42
3rdparty/WinCommander.hpp vendored Normal file
View 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 &parameters = QStringList(),
const QString &workingDir = QString(),
bool aWait = true);
static uint runProcessElevated(const QString &path,
const QString &parameters = QString(),
const QString &workingDir = QString(),
bool aWait = true);
};
#endif // WINCOMMANDER_H

386
3rdparty/ZxingQtReader.hpp vendored Normal file
View 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
View 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
View 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
View 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
View 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 下载
[![GitHub All Releases](https://img.shields.io/github/downloads/Matsuridayo/nekoray/total?label=downloads-total&logo=github&style=flat-square)](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

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

BIN
assets/qtbase_zh_CN.qm Normal file

Binary file not shown.

View 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

View 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

View 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 ()

View 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
View 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
View 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
View 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
View File

@@ -0,0 +1 @@
edf4fcff77a0dfd40b20076faf45767a3394f5eb

475
db/ConfigBuilder.cpp Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1 @@
tun2socks

5
examples/build-alpine.md Normal file
View 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
View 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
View 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
View 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
View 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

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;
};
}

View 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
View 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
View 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
View File

@@ -0,0 +1,6 @@
*.log
*.pem
*.json
*.exe
*.dat
/nekoray_core

54
go/auth.go Normal file
View 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
View 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

File diff suppressed because it is too large Load Diff

96
go/gen/libcore.proto Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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"
)

View 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
}

View 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")
}

View 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
View 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
}

View 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
View 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{}
}

View 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
}

View 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
View 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
View File

@@ -0,0 +1,6 @@
//go:build !windows && !linux
package main
func ToolBox() {
}

26
go/toolbox_windows.go Normal file
View 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
View 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
View 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
View File

@@ -0,0 +1,2 @@
deps
downloaded

6
libs/README Normal file
View File

@@ -0,0 +1,6 @@
依赖
libs/deps/*
libs/deps/windows-x64 (vcpkg)
libs/deps/built (prefix)
全部在项目根目录运行

54
libs/build_deps_all.sh Executable file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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", &current_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
View File

@@ -0,0 +1,6 @@
#pragma once
#include "Const.hpp"
#include "NekoRay_Utils.hpp"
#include "NekoRay_ConfigItem.hpp"
#include "NekoRay_DataStore.hpp"

View 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
View 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
View 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
View 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