This commit is contained in:
Fabian Freund
2025-08-29 13:27:23 +02:00
parent 917af47579
commit caef036422
23 changed files with 665 additions and 7 deletions
@@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx
@@ -0,0 +1,70 @@
group = "eu.weblibre.pluggable_transports_proxy"
version = "1.0-SNAPSHOT"
buildscript {
ext.kotlin_version = "2.1.0"
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.9.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: "com.android.library"
apply plugin: "kotlin-android"
android {
namespace = "eu.weblibre.pluggable_transports_proxy"
compileSdk = 36
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11
}
sourceSets {
main.java.srcDirs += "src/main/kotlin"
test.java.srcDirs += "src/test/kotlin"
}
defaultConfig {
minSdk = 24
}
dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.mockito:mockito-core:5.0.0")
}
testOptions {
unitTests.all {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
outputs.upToDateWhen {false}
showStandardStreams = true
}
}
}
}
dependencies {
implementation 'com.netzarchitekten:IPtProxy:4.2.2'
}
@@ -0,0 +1 @@
rootProject.name = 'pluggable_transports_proxy'
@@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.weblibre.pluggable_transports_proxy">
</manifest>
@@ -0,0 +1,34 @@
package eu.weblibre.pluggable_transports_proxy
import IPtProxy.Controller
import eu.weblibre.pluggable_transports_proxy.pigeons.IPtProxyController
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodChannel
import java.io.File
/** PluggableTransportsProxyPlugin */
class PluggableTransportsProxyPlugin : FlutterPlugin {
private lateinit var channel: MethodChannel
companion object {
@Volatile
private var INSTANCE: Controller? = null
fun getController(ptDirPath: String): Controller {
return INSTANCE ?: synchronized(this) {
INSTANCE ?: Controller(ptDirPath, true, false, "INFO", null).also { INSTANCE = it }
}
}
}
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
val ptDir = File(flutterPluginBinding.applicationContext.cacheDir, "pt_state")
val ptc = getController(ptDir.path)
IPtProxyController.setUp(flutterPluginBinding.binaryMessenger, ProxyImpl(controller = ptc))
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
// Controller instance remains alive for reuse
}
}
@@ -0,0 +1,33 @@
package eu.weblibre.pluggable_transports_proxy
import eu.weblibre.pluggable_transports_proxy.pigeons.IPtProxyController
import eu.weblibre.pluggable_transports_proxy.pigeons.ProxyType
class ProxyImpl(val controller: IPtProxy.Controller) : IPtProxyController {
override fun start(
proxyType: ProxyType,
proxy: String
): Long {
val type = when (proxyType) {
ProxyType.OBFS4 -> IPtProxy.IPtProxy.Obfs4
ProxyType.MEEK_LITE -> IPtProxy.IPtProxy.MeekLite
ProxyType.WEBTUNNEL -> IPtProxy.IPtProxy.Webtunnel
ProxyType.SNOWFLAKE -> IPtProxy.IPtProxy.Snowflake
};
controller.start(type, proxy)
return controller.port(type)
}
override fun stop(proxyType: ProxyType) {
val type = when (proxyType) {
ProxyType.OBFS4 -> IPtProxy.IPtProxy.Obfs4
ProxyType.MEEK_LITE -> IPtProxy.IPtProxy.MeekLite
ProxyType.WEBTUNNEL -> IPtProxy.IPtProxy.Webtunnel
ProxyType.SNOWFLAKE -> IPtProxy.IPtProxy.Snowflake
};
controller.stop(type)
}
}
@@ -0,0 +1,137 @@
// Autogenerated from Pigeon (v26.0.1), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
package eu.weblibre.pluggable_transports_proxy.pigeons
import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMethodCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
private object ProxyPigeonUtils {
fun wrapResult(result: Any?): List<Any?> {
return listOf(result)
}
fun wrapError(exception: Throwable): List<Any?> {
return if (exception is FlutterError) {
listOf(
exception.code,
exception.message,
exception.details
)
} else {
listOf(
exception.javaClass.simpleName,
exception.toString(),
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
)
}
}
}
/**
* Error class for passing custom error details to Flutter via a thrown PlatformException.
* @property code The error code.
* @property message The error message.
* @property details The error details. Must be a datatype supported by the api codec.
*/
class FlutterError (
val code: String,
override val message: String? = null,
val details: Any? = null
) : Throwable()
enum class ProxyType(val raw: Int) {
OBFS4(0),
MEEK_LITE(1),
WEBTUNNEL(2),
SNOWFLAKE(3);
companion object {
fun ofRaw(raw: Int): ProxyType? {
return values().firstOrNull { it.raw == raw }
}
}
}
private open class ProxyPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
129.toByte() -> {
return (readValue(buffer) as Long?)?.let {
ProxyType.ofRaw(it.toInt())
}
}
else -> super.readValueOfType(type, buffer)
}
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
when (value) {
is ProxyType -> {
stream.write(129)
writeValue(stream, value.raw)
}
else -> super.writeValue(stream, value)
}
}
}
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
interface IPtProxyController {
fun start(proxyType: ProxyType, proxy: String): Long
fun stop(proxyType: ProxyType)
companion object {
/** The codec used by IPtProxyController. */
val codec: MessageCodec<Any?> by lazy {
ProxyPigeonCodec()
}
/** Sets up an instance of `IPtProxyController` to handle messages through the `binaryMessenger`. */
@JvmOverloads
fun setUp(binaryMessenger: BinaryMessenger, api: IPtProxyController?, messageChannelSuffix: String = "") {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.pluggable_transports_proxy.IPtProxyController.start$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val proxyTypeArg = args[0] as ProxyType
val proxyArg = args[1] as String
val wrapped: List<Any?> = try {
listOf(api.start(proxyTypeArg, proxyArg))
} catch (exception: Throwable) {
ProxyPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.pluggable_transports_proxy.IPtProxyController.stop$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val proxyTypeArg = args[0] as ProxyType
val wrapped: List<Any?> = try {
api.stop(proxyTypeArg)
listOf(null)
} catch (exception: Throwable) {
ProxyPigeonUtils.wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}