diff --git a/README.md b/README.md
index ddf773c..4048cee 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,7 @@
# RKNHardering
+
Android-приложение для обнаружения VPN и прокси на устройстве. Реализует методику РКН по выявлению средств обхода блокировок.
@@ -25,7 +26,7 @@ Android-приложение для обнаружения VPN и прокси
- **Обход нативных проверок** (противодействие JNI-проверкам через `/proc/self/maps`, `getifaddrs()`, `dlsym`)
- **Маскировка установленных приложений** (скрытие пакетов VPN-приложений от `PackageManager`)
-Если вы обладаете знаниями в этих областях, пожалуйста, откройте Issue или Pull Request, либо напишите в [чат Matrix](https://matrix.to/#/%23RKN_Hardering:matrix.kangel.tech) с описанием метода, условий применимости и ограничений. Любая информация ценна — от теоретических идей до работающих PoC.
+Если вы обладаете знаниями в этих областях, пожалуйста, откройте Issue или Pull Request, либо напишите в [чат Matrix](https://matrix.to/#/%23RKN_Hardering:matrix.kangel.tech)/[Telegram](https://t.me/RKNHardering) с описанием метода, условий применимости и ограничений. Любая информация ценна — от теоретических идей до работающих PoC.
> **EN**
@@ -42,7 +43,7 @@ I am looking for people willing to help collect, organize, and test information
- **Bypassing native checks** (countering JNI-based checks through `/proc/self/maps`, `getifaddrs()`, and `dlsym`)
- **Masking installed applications** (hiding VPN app packages from `PackageManager`)
-If you have expertise in these areas, please open an Issue or Pull Request describing the method, the conditions under which it applies, and its limitations. Any information is valuable, from theoretical ideas to working PoCs.
+If you have expertise in these areas, please open an Issue or [Сhat in Matrix](https://matrix.to/#/%23RKN_Hardering:matrix.kangel.tech)/[Telegram](https://t.me/RKNHardering), the conditions under which it applies, and its limitations. Any information is valuable, from theoretical ideas to working PoCs.
## Архитектура
diff --git a/app/src/main/java/com/notcvnt/rknhardering/MainActivity.kt b/app/src/main/java/com/notcvnt/rknhardering/MainActivity.kt
index ce72b5e..512ead7 100644
--- a/app/src/main/java/com/notcvnt/rknhardering/MainActivity.kt
+++ b/app/src/main/java/com/notcvnt/rknhardering/MainActivity.kt
@@ -1869,7 +1869,7 @@ class MainActivity : AppCompatActivity() {
private fun ensureCardVisible(
card: MaterialCardView,
- animate: Boolean = true,
+ @Suppress("UNUSED_PARAMETER") animate: Boolean = true,
shouldAutoScroll: Boolean = false,
) {
val inHiddenHost = card.parent === hiddenLegacyCardsHost
@@ -2704,14 +2704,6 @@ class MainActivity : AppCompatActivity() {
} else {
getString(R.string.main_card_call_transport_stun_none_responded)
}
- val groupResult = com.notcvnt.rknhardering.model.IpCheckerGroupResult(
- title = groupTitle,
- detected = false,
- needsReview = false,
- statusLabel = statusLabel,
- summary = statusLabel,
- responses = emptyList(),
- )
val card = com.google.android.material.card.MaterialCardView(themedContext()).apply {
layoutParams = LinearLayout.LayoutParams(
diff --git a/app/src/main/java/com/notcvnt/rknhardering/SettingsAboutFragment.kt b/app/src/main/java/com/notcvnt/rknhardering/SettingsAboutFragment.kt
index ca75d72..0cdc169 100644
--- a/app/src/main/java/com/notcvnt/rknhardering/SettingsAboutFragment.kt
+++ b/app/src/main/java/com/notcvnt/rknhardering/SettingsAboutFragment.kt
@@ -20,6 +20,10 @@ internal class SettingsAboutFragment : Fragment(R.layout.fragment_settings_about
startActivity(Intent(Intent.ACTION_VIEW, getString(R.string.matrix_room_url).toUri()))
}
+ view.findViewById(R.id.cardTelegram).setOnClickListener {
+ startActivity(Intent(Intent.ACTION_VIEW, getString(R.string.telegram_group_url).toUri()))
+ }
+
view.findViewById(R.id.cardGithub).setOnClickListener {
startActivity(Intent(Intent.ACTION_VIEW, getString(R.string.github_repo_url).toUri()))
}
diff --git a/app/src/main/java/com/notcvnt/rknhardering/SettingsCategoriesFragment.kt b/app/src/main/java/com/notcvnt/rknhardering/SettingsCategoriesFragment.kt
index fdc545a..8aadf48 100644
--- a/app/src/main/java/com/notcvnt/rknhardering/SettingsCategoriesFragment.kt
+++ b/app/src/main/java/com/notcvnt/rknhardering/SettingsCategoriesFragment.kt
@@ -138,7 +138,7 @@ internal class SettingsCategoriesFragment : Fragment(R.layout.fragment_settings_
"dark" -> getString(R.string.settings_theme_dark)
else -> getString(R.string.settings_theme_system)
}
- val language = when (val stored = prefs.getString(SettingsPrefs.PREF_LANGUAGE, "").orEmpty()) {
+ val language = when (prefs.getString(SettingsPrefs.PREF_LANGUAGE, "").orEmpty()) {
"en" -> "EN"
"ru" -> "RU"
"fa" -> "FA"
diff --git a/app/src/main/java/com/notcvnt/rknhardering/checker/CdnPullingChecker.kt b/app/src/main/java/com/notcvnt/rknhardering/checker/CdnPullingChecker.kt
index 1ce19d5..b05da3b 100644
--- a/app/src/main/java/com/notcvnt/rknhardering/checker/CdnPullingChecker.kt
+++ b/app/src/main/java/com/notcvnt/rknhardering/checker/CdnPullingChecker.kt
@@ -195,7 +195,6 @@ object CdnPullingChecker {
val successfulCount = successfulResponses.size
val actionableResponses = successfulResponses.filter { it.targetLabel in ACTIONABLE_TARGETS }
val actionableCount = actionableResponses.size
- val actionableTargetCount = responses.count { it.targetLabel in ACTIONABLE_TARGETS }
val allIpv4s = successfulResponses.mapNotNull { it.ipv4 ?: it.ip?.takeIf { ip -> CdnPullingClient.looksLikeIpv4(ip) } }.distinct()
val allIpv6s = successfulResponses.mapNotNull { it.ipv6 ?: it.ip?.takeIf { ip -> CdnPullingClient.looksLikeIpv6(ip) } }.distinct()
diff --git a/app/src/main/java/com/notcvnt/rknhardering/checker/DirectSignsChecker.kt b/app/src/main/java/com/notcvnt/rknhardering/checker/DirectSignsChecker.kt
index ee47808..1a2e4b0 100644
--- a/app/src/main/java/com/notcvnt/rknhardering/checker/DirectSignsChecker.kt
+++ b/app/src/main/java/com/notcvnt/rknhardering/checker/DirectSignsChecker.kt
@@ -260,6 +260,7 @@ object DirectSignsChecker {
return SignalOutcome(detected = detected, needsReview = needsReview)
}
+ @Suppress("DEPRECATION")
private fun collectProxyInfoProfiles(context: Context): ProxyProfileCollection {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
@@ -734,7 +735,7 @@ object DirectSignsChecker {
val hasDnsPathMismatch = comparison?.dnsPathMismatch == true
if (hasDnsPathMismatch) {
val transportOnly = comparison!!.usedCurlCompatibleFallback() &&
- comparison!!.curlCompatible!!.transportDiagnostics.resolveStrategy != TunProbeResolveStrategy.KOTLIN_INJECTED
+ comparison.curlCompatible.transportDiagnostics.resolveStrategy != TunProbeResolveStrategy.KOTLIN_INJECTED
val confidence = if (transportOnly) EvidenceConfidence.MEDIUM else EvidenceConfidence.HIGH
evidence.add(
EvidenceItem(
diff --git a/app/src/main/java/com/notcvnt/rknhardering/checker/IndirectSignsChecker.kt b/app/src/main/java/com/notcvnt/rknhardering/checker/IndirectSignsChecker.kt
index 9280549..d40c39a 100644
--- a/app/src/main/java/com/notcvnt/rknhardering/checker/IndirectSignsChecker.kt
+++ b/app/src/main/java/com/notcvnt/rknhardering/checker/IndirectSignsChecker.kt
@@ -333,8 +333,7 @@ object IndirectSignsChecker {
capsString = capsString,
routes = linkProperties.routes.map { route ->
RouteSnapshot(
- destination = route.destination?.toString()
- ?: if (route.isDefaultRoute) "0.0.0.0/0" else "unknown",
+ destination = route.destination.toString(),
gateway = route.gateway?.hostAddress?.takeUnless { it == "0.0.0.0" || it == "::" },
interfaceName = NetworkInterfaceNameNormalizer.canonicalName(
route.`interface` ?: linkProperties.interfaceName,
diff --git a/app/src/main/java/com/notcvnt/rknhardering/checker/LocationSignalsChecker.kt b/app/src/main/java/com/notcvnt/rknhardering/checker/LocationSignalsChecker.kt
index 0f663d1..ad74409 100644
--- a/app/src/main/java/com/notcvnt/rknhardering/checker/LocationSignalsChecker.kt
+++ b/app/src/main/java/com/notcvnt/rknhardering/checker/LocationSignalsChecker.kt
@@ -1,3 +1,5 @@
+@file:Suppress("DEPRECATION")
+
package com.notcvnt.rknhardering.checker
import android.Manifest
diff --git a/app/src/main/java/com/notcvnt/rknhardering/checker/VpnCheckRunner.kt b/app/src/main/java/com/notcvnt/rknhardering/checker/VpnCheckRunner.kt
index 881a8fa..4b7f481 100644
--- a/app/src/main/java/com/notcvnt/rknhardering/checker/VpnCheckRunner.kt
+++ b/app/src/main/java/com/notcvnt/rknhardering/checker/VpnCheckRunner.kt
@@ -178,7 +178,7 @@ object VpnCheckRunner {
}
private object Fallbacks {
- fun geoIp(context: Context, error: Throwable): CategoryResult = CategoryResult(
+ fun geoIp(@Suppress("UNUSED_PARAMETER") context: Context, error: Throwable): CategoryResult = CategoryResult(
name = "GeoIP",
detected = false,
findings = listOf(Finding(error.message ?: error::class.java.simpleName, isError = true)),
@@ -243,18 +243,18 @@ object VpnCheckRunner {
needsReview = true,
findings = listOf(Finding(error.message ?: error::class.java.simpleName, isError = true)),
)
- fun indirect(context: Context, error: Throwable): CategoryResult = CategoryResult(
+ fun indirect(@Suppress("UNUSED_PARAMETER") context: Context, error: Throwable): CategoryResult = CategoryResult(
name = "Indirect",
detected = false,
needsReview = true,
findings = listOf(Finding(error.message ?: error::class.java.simpleName, isError = true)),
)
- fun location(context: Context, error: Throwable): CategoryResult = CategoryResult(
+ fun location(@Suppress("UNUSED_PARAMETER") context: Context, error: Throwable): CategoryResult = CategoryResult(
name = "Location",
detected = false,
findings = listOf(Finding(error.message ?: error::class.java.simpleName, isError = true)),
)
- fun native(context: Context, error: Throwable): CategoryResult = CategoryResult(
+ fun native(@Suppress("UNUSED_PARAMETER") context: Context, error: Throwable): CategoryResult = CategoryResult(
name = "Native",
detected = false,
findings = listOf(Finding(error.message ?: error::class.java.simpleName, isError = true)),
diff --git a/app/src/main/java/com/notcvnt/rknhardering/checker/ipconsensus/AsnResolver.kt b/app/src/main/java/com/notcvnt/rknhardering/checker/ipconsensus/AsnResolver.kt
index cc156e7..01461e3 100644
--- a/app/src/main/java/com/notcvnt/rknhardering/checker/ipconsensus/AsnResolver.kt
+++ b/app/src/main/java/com/notcvnt/rknhardering/checker/ipconsensus/AsnResolver.kt
@@ -44,7 +44,7 @@ class AsnResolver(
const val BATCH_TIMEOUT_MS = 5_000L
const val PER_REQUEST_TIMEOUT_MS = 3_000L
- fun default(context: Context, resolverConfig: DnsResolverConfig): AsnResolver {
+ fun default(@Suppress("UNUSED_PARAMETER") context: Context, resolverConfig: DnsResolverConfig): AsnResolver {
return AsnResolver(
lookup = { ip -> lookupViaIpapiIs(ip, resolverConfig) },
)
diff --git a/app/src/main/java/com/notcvnt/rknhardering/checker/ipconsensus/IpConsensusBuilder.kt b/app/src/main/java/com/notcvnt/rknhardering/checker/ipconsensus/IpConsensusBuilder.kt
index 53823b7..ced2ce8 100644
--- a/app/src/main/java/com/notcvnt/rknhardering/checker/ipconsensus/IpConsensusBuilder.kt
+++ b/app/src/main/java/com/notcvnt/rknhardering/checker/ipconsensus/IpConsensusBuilder.kt
@@ -145,8 +145,8 @@ object IpConsensusBuilder {
channel = Channel.DIRECT,
source = "geoip",
targetGroup = null,
- countryCode = geoIp.geoFacts?.countryCode,
- asn = geoIp.geoFacts?.asn,
+ countryCode = geoIp.geoFacts.countryCode,
+ asn = geoIp.geoFacts.asn,
)
}
diff --git a/app/src/main/java/com/notcvnt/rknhardering/network/ResolverNetworkStack.kt b/app/src/main/java/com/notcvnt/rknhardering/network/ResolverNetworkStack.kt
index 6e9c2e6..e25e049 100644
--- a/app/src/main/java/com/notcvnt/rknhardering/network/ResolverNetworkStack.kt
+++ b/app/src/main/java/com/notcvnt/rknhardering/network/ResolverNetworkStack.kt
@@ -529,7 +529,7 @@ internal class DirectDns(
}
}
if (!typeResolved && type == 1 && resolved.isEmpty() && lastFailure is UnknownHostException) {
- throw lastFailure as UnknownHostException
+ throw lastFailure
}
}
diff --git a/app/src/main/java/com/notcvnt/rknhardering/probe/LocalSocketInspector.kt b/app/src/main/java/com/notcvnt/rknhardering/probe/LocalSocketInspector.kt
index 9dfb420..52fbd4e 100644
--- a/app/src/main/java/com/notcvnt/rknhardering/probe/LocalSocketInspector.kt
+++ b/app/src/main/java/com/notcvnt/rknhardering/probe/LocalSocketInspector.kt
@@ -124,7 +124,7 @@ object LocalSocketInspector {
@Suppress("DEPRECATION")
pm.getApplicationInfo(packageName, 0)
}
- pm.getApplicationLabel(appInfo)?.toString()
+ pm.getApplicationLabel(appInfo).toString()
}.getOrNull()
}
diff --git a/app/src/main/java/com/notcvnt/rknhardering/probe/NativeCurlBridge.kt b/app/src/main/java/com/notcvnt/rknhardering/probe/NativeCurlBridge.kt
index 95cc3cf..713756e 100644
--- a/app/src/main/java/com/notcvnt/rknhardering/probe/NativeCurlBridge.kt
+++ b/app/src/main/java/com/notcvnt/rknhardering/probe/NativeCurlBridge.kt
@@ -78,7 +78,7 @@ object NativeCurlBridge {
if (!isLibraryLoaded()) {
return NativeCurlResponse(localError = lastLoadErrorMessage() ?: "Native curl bridge is not loaded")
}
- val activeCaBundle = request.caBundlePath?.takeIf { it.isNotBlank() } ?: caBundleInfo?.absolutePath
+ val activeCaBundle = request.caBundlePath.takeIf { it.isNotBlank() } ?: caBundleInfo?.absolutePath
if (activeCaBundle.isNullOrBlank()) {
return NativeCurlResponse(localError = "Native CA bundle is unavailable")
}
diff --git a/app/src/main/java/com/notcvnt/rknhardering/probe/UnderlyingNetworkProber.kt b/app/src/main/java/com/notcvnt/rknhardering/probe/UnderlyingNetworkProber.kt
index 5ebf9db..5149795 100644
--- a/app/src/main/java/com/notcvnt/rknhardering/probe/UnderlyingNetworkProber.kt
+++ b/app/src/main/java/com/notcvnt/rknhardering/probe/UnderlyingNetworkProber.kt
@@ -509,6 +509,7 @@ object UnderlyingNetworkProber {
)
}
+ @Suppress("DEPRECATION")
private fun buildProbeEnvironment(context: Context): ProbeEnvironment {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networks = cm.allNetworks.mapNotNull { network ->
diff --git a/app/src/main/java/com/notcvnt/rknhardering/vpn/InstalledVpnAppDetector.kt b/app/src/main/java/com/notcvnt/rknhardering/vpn/InstalledVpnAppDetector.kt
index 8952032..9930c57 100644
--- a/app/src/main/java/com/notcvnt/rknhardering/vpn/InstalledVpnAppDetector.kt
+++ b/app/src/main/java/com/notcvnt/rknhardering/vpn/InstalledVpnAppDetector.kt
@@ -282,7 +282,7 @@ object InstalledVpnAppDetector {
return resolveInfos
.asSequence()
- .mapNotNull { it.loadLabel(pm)?.toString()?.trim() }
+ .mapNotNull { it.loadLabel(pm).toString().trim() }
.firstOrNull { it.isNotBlank() }
}
diff --git a/app/src/main/java/com/notcvnt/rknhardering/vpn/VpnAppMetadataScanner.kt b/app/src/main/java/com/notcvnt/rknhardering/vpn/VpnAppMetadataScanner.kt
index 60877ef..9d6d542 100644
--- a/app/src/main/java/com/notcvnt/rknhardering/vpn/VpnAppMetadataScanner.kt
+++ b/app/src/main/java/com/notcvnt/rknhardering/vpn/VpnAppMetadataScanner.kt
@@ -57,7 +57,7 @@ object VpnAppMetadataScanner {
@Suppress("DEPRECATION")
pm.getApplicationInfo(packageName, 0)
}
- pm.getApplicationLabel(appInfo)?.toString()?.trim()?.takeIf { it.isNotBlank() }
+ pm.getApplicationLabel(appInfo).toString().trim().takeIf { it.isNotBlank() }
}.getOrNull()
}
diff --git a/app/src/main/res/drawable/ic_telegram.xml b/app/src/main/res/drawable/ic_telegram.xml
new file mode 100644
index 0000000..a33d082
--- /dev/null
+++ b/app/src/main/res/drawable/ic_telegram.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/layout/fragment_settings_about.xml b/app/src/main/res/layout/fragment_settings_about.xml
index fe7e2fb..de71d53 100644
--- a/app/src/main/res/layout/fragment_settings_about.xml
+++ b/app/src/main/res/layout/fragment_settings_about.xml
@@ -113,6 +113,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
GitHub
Matrix
RKN_Hardering:matrix.kangel.tech
+ https://t.me/RKNHardering
+ Telegram
+ @RKNHardering
کپی تشخیص
تشخیص کپی شد
خروجی
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 675776b..b5f9ef2 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -127,6 +127,9 @@
xtclovver/RKNHardering
Matrix
RKN_Hardering:matrix.kangel.tech
+ https://t.me/RKNHardering
+ Telegram
+ @RKNHardering
От
До
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index a41a351..ccd6278 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -115,6 +115,9 @@
GitHub
Matrix
RKN_Hardering:matrix.kangel.tech
+ https://t.me/RKNHardering
+ Telegram
+ @RKNHardering
复制诊断
诊断已复制
导出
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b12ba5a..2cea875 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -126,6 +126,9 @@
xtclovver/RKNHardering
Matrix
RKN_Hardering:matrix.kangel.tech
+ https://t.me/RKNHardering
+ Telegram
+ @RKNHardering
From
To
diff --git a/docs/README.en.md b/docs/README.en.md
index c97a9ea..8c38184 100644
--- a/docs/README.en.md
+++ b/docs/README.en.md
@@ -3,6 +3,7 @@
# RKNHardering
+
Android app for detecting VPNs and proxies on a device. Implements the Roskomnadzor-style methodology for identifying censorship circumvention tools.
@@ -23,7 +24,7 @@ I am looking for people willing to help collect, organize, and test information
- **Bypassing native checks** (countering JNI-based checks through `/proc/self/maps`, `getifaddrs()`, and `dlsym`)
- **Masking installed applications** (hiding VPN app packages from `PackageManager`)
-If you have expertise in these areas, please open an Issue or Pull Request, or reach out in the [Matrix chat](https://matrix.to/#/%23RKN_Hardering:matrix.kangel.tech) describing the method, the conditions under which it applies, and its limitations. Any information is valuable, from theoretical ideas to working PoCs.
+If you have expertise in these areas, please open an Issue or Pull Request, or reach out in the [Matrix chat](https://matrix.to/#/%23RKN_Hardering:matrix.kangel.tech)/[Telegram](https://t.me/RKNHardering) describing the method, the conditions under which it applies, and its limitations. Any information is valuable, from theoretical ideas to working PoCs.
## Architecture
diff --git a/docs/README.fa.md b/docs/README.fa.md
index 1c049ba..7b30244 100644
--- a/docs/README.fa.md
+++ b/docs/README.fa.md
@@ -3,6 +3,7 @@
# RKNHardering
+
برنامه Android برای شناسایی VPN و proxy روی دستگاه. این پروژه روش مبتنی بر منطق روسکومنادزور برای تشخیص ابزارهای دور زدن مسدودسازی را پیادهسازی میکند.
@@ -23,7 +24,7 @@
- **دور زدن بررسیهای بومی/native** (مقابله با بررسیهای مبتنی بر JNI از طریق `/proc/self/maps`، `getifaddrs()` و `dlsym`)
- **پنهانسازی برنامههای نصبشده** (مخفی کردن بستههای برنامه VPN از `PackageManager`)
-اگر در این زمینهها تخصص دارید، لطفاً یک Issue یا Pull Request باز کنید، یا در [چت Matrix](https://matrix.to/#/%23RKN_Hardering:matrix.kangel.tech) روش خود را همراه با شرایط کاربرد و محدودیتهای آن شرح دهید. هر اطلاعاتی ارزشمند است — از ایدههای تئوری تا PoCهای کاربردی.
+اگر در این زمینهها تخصص دارید، لطفاً یک Issue یا Pull Request باز کنید، یا در [چت Matrix](https://matrix.to/#/%23RKN_Hardering:matrix.kangel.tech)/[Telegram](https://t.me/RKNHardering) روش خود را همراه با شرایط کاربرد و محدودیتهای آن شرح دهید. هر اطلاعاتی ارزشمند است — از ایدههای تئوری تا PoCهای کاربردی.
## معماری
diff --git a/docs/README.zh-CN.md b/docs/README.zh-CN.md
index 2bfc1ee..4fc7dee 100644
--- a/docs/README.zh-CN.md
+++ b/docs/README.zh-CN.md
@@ -3,6 +3,7 @@
# RKNHardering
+
用于检测设备上 VPN 和代理的 Android 应用。该项目实现了一套类似 Roskomnadzor 的封锁绕过工具识别方法。
@@ -23,7 +24,7 @@
- **绕过原生检查**(对抗通过 `/proc/self/maps`、`getifaddrs()`、`dlsym` 进行的 JNI 检查)
- **隐藏已安装的应用**(对 `PackageManager` 隐藏 VPN 应用的包名)
-如果你在这些领域有专业知识,请提交 Issue 或 Pull Request,或在 [Matrix 聊天室](https://matrix.to/#/%23RKN_Hardering:matrix.kangel.tech)中描述方法、适用条件和限制。任何信息都很有价值——从理论想法到可用的 PoC。
+如果你在这些领域有专业知识,请提交 Issue 或 Pull Request,或在 [Matrix 聊天室](https://matrix.to/#/%23RKN_Hardering:matrix.kangel.tech)/[Telegram](https://t.me/RKNHardering) 中描述方法、适用条件和限制。任何信息都很有价值——从理论想法到可用的 PoC。
## 架构