Skip to content

Commit 6b36902

Browse files
authored
Merge pull request #30 from mysteriumnetwork/10-tunnel-status
10 tunnel status
2 parents b05768c + 3c347c1 commit 6b36902

27 files changed

+443
-377
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ build/
77
# If you're building an application, you may want to check-in your pubspec.lock
88
pubspec.lock
99

10+
.idea
11+
1012
# Directory created by dartdoc
1113
# If you don't generate documentation locally you can remove this line.
1214
doc/api/
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package network.mysterium.wireguard_dart
2+
3+
import com.wireguard.android.backend.Tunnel
4+
5+
enum class ConnectionStatus {
6+
disconnected, connected, connecting, disconnecting, unknown;
7+
8+
companion object {
9+
fun fromTunnelState(state: Tunnel.State?): ConnectionStatus = when (state) {
10+
Tunnel.State.UP -> connected
11+
Tunnel.State.DOWN -> disconnected
12+
else -> unknown
13+
}
14+
}
15+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package network.mysterium.wireguard_dart
2+
3+
import android.os.Handler
4+
import android.os.Looper
5+
import io.flutter.plugin.common.EventChannel
6+
import io.flutter.plugin.common.EventChannel.EventSink
7+
import io.flutter.plugin.common.EventChannel.StreamHandler
8+
9+
class ConnectionStatusBroadcaster : StreamHandler {
10+
11+
private var eventSink: EventSink? = null
12+
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
13+
eventSink = events
14+
}
15+
16+
override fun onCancel(arguments: Any?) {
17+
eventSink = null
18+
}
19+
20+
fun send(status: ConnectionStatus) {
21+
Handler(Looper.getMainLooper()).post {
22+
eventSink?.success(hashMapOf("status" to status.name))
23+
}
24+
}
25+
}

android/src/main/kotlin/network/mysterium/wireguard_dart/WireguardDartPlugin.kt

Lines changed: 95 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,22 @@ import android.content.Context
1717
import android.util.Log
1818
import com.beust.klaxon.Klaxon
1919
import com.wireguard.android.backend.*
20-
import com.wireguard.crypto.Key
2120
import com.wireguard.crypto.KeyPair
21+
import io.flutter.plugin.common.EventChannel
2222
import kotlinx.coroutines.*
23-
import java.util.*
2423

2524

2625
import kotlinx.coroutines.launch
2726
import java.io.ByteArrayInputStream
2827

29-
/** WireguardDartPlugin */
30-
3128
const val PERMISSIONS_REQUEST_CODE = 10014
32-
const val METHOD_CHANNEL_NAME = "wireguard_dart"
3329

3430
class WireguardDartPlugin : FlutterPlugin, MethodCallHandler, ActivityAware,
35-
PluginRegistry.ActivityResultListener {
36-
/// The MethodChannel that will the communication between Flutter and native Android
37-
///
38-
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
39-
/// when the Flutter Engine is detached from the Activity
31+
PluginRegistry.ActivityResultListener {
32+
4033
private lateinit var channel: MethodChannel
41-
private lateinit var tunnelName: String
34+
private lateinit var statusChannel: EventChannel
35+
private lateinit var statusBroadcaster: ConnectionStatusBroadcaster
4236
private val futureBackend = CompletableDeferred<Backend>()
4337
private val scope = CoroutineScope(Job() + Dispatchers.Main.immediate)
4438
private var backend: Backend? = null
@@ -47,15 +41,21 @@ class WireguardDartPlugin : FlutterPlugin, MethodCallHandler, ActivityAware,
4741
private var activity: Activity? = null
4842
private var config: com.wireguard.config.Config? = null
4943
private var tunnel: WireguardTunnel? = null
50-
44+
private var status: ConnectionStatus = ConnectionStatus.disconnected
45+
set(value) {
46+
field = value
47+
if (::statusBroadcaster.isInitialized) {
48+
statusBroadcaster.send(value)
49+
}
50+
}
5151

5252
companion object {
5353
const val TAG = "MainActivity"
5454
}
5555

5656
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
5757
havePermission =
58-
(requestCode == PERMISSIONS_REQUEST_CODE) && (resultCode == Activity.RESULT_OK)
58+
(requestCode == PERMISSIONS_REQUEST_CODE) && (resultCode == Activity.RESULT_OK)
5959
return havePermission
6060
}
6161

@@ -76,7 +76,10 @@ class WireguardDartPlugin : FlutterPlugin, MethodCallHandler, ActivityAware,
7676
}
7777

7878
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
79-
channel = MethodChannel(flutterPluginBinding.binaryMessenger, METHOD_CHANNEL_NAME)
79+
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "wireguard_dart")
80+
statusChannel = EventChannel(flutterPluginBinding.binaryMessenger, "wireguard_dart.status")
81+
statusBroadcaster = ConnectionStatusBroadcaster()
82+
statusChannel.setStreamHandler(statusBroadcaster)
8083
context = flutterPluginBinding.applicationContext
8184

8285
scope.launch(Dispatchers.IO) {
@@ -121,115 +124,109 @@ class WireguardDartPlugin : FlutterPlugin, MethodCallHandler, ActivityAware,
121124
"setupTunnel" -> setupTunnel(call.argument<String>("bundleId").toString(), result)
122125
"connect" -> connect(call.argument<String>("cfg").toString(), result)
123126
"disconnect" -> disconnect(result)
124-
"getStats" -> handleGetStats(call.arguments, result)
127+
"status" -> status(result)
128+
"statistics" -> statistics(result)
125129
else -> flutterNotImplemented(result)
126130
}
127131
}
128132

129-
private fun handleGetStats(arguments: Any?, result: Result) {
130-
val tunnelName = arguments?.toString()
131-
if (tunnelName.isNullOrEmpty()) {
132-
flutterError(result, "Tunnel has not been initialized")
133-
return
134-
}
133+
private fun generateKeyPair(result: Result) {
134+
val keyPair = KeyPair()
135+
result.success(
136+
hashMapOf(
137+
"privateKey" to keyPair.privateKey.toBase64(),
138+
"publicKey" to keyPair.publicKey.toBase64()
139+
)
140+
)
141+
}
135142

143+
private fun setupTunnel(bundleId: String, result: Result) {
136144
scope.launch(Dispatchers.IO) {
145+
if (Tunnel.isNameInvalid(bundleId)) {
146+
flutterError(result, "Invalid Name")
147+
return@launch
148+
}
149+
checkPermission()
150+
tunnel = WireguardTunnel(bundleId) { state ->
151+
status = ConnectionStatus.fromTunnelState(state)
152+
}
153+
status = ConnectionStatus.fromTunnelState(backend?.getState(tunnel!!))
154+
result.success(null)
155+
}
156+
}
137157

158+
private fun connect(cfg: String, result: Result) {
159+
val tun = tunnel ?: run {
160+
result.error("err_setup_tunnel", "Tunnel is not initialized", null)
161+
return
162+
}
163+
status = ConnectionStatus.connecting
164+
scope.launch(Dispatchers.IO) {
138165
try {
139-
val statistics = futureBackend.await().getStatistics(tunnel(tunnelName))
140-
val stats = Stats(statistics.totalRx(), statistics.totalTx())
141-
142-
flutterSuccess(
143-
result, Klaxon().toJsonString(
144-
stats
145-
)
146-
)
147-
Log.i(TAG, "Statistics - ${stats.totalDownload} ${stats.totalUpload}")
148-
149-
} catch (e: BackendException) {
150-
Log.e(TAG, "Statistics - BackendException - ERROR - ${e.reason} ", e)
151-
flutterError(result, e.reason.toString())
166+
if (!havePermission) {
167+
checkPermission()
168+
throw Exception("Permissions are not given")
169+
}
170+
val inputStream = ByteArrayInputStream(cfg.toByteArray())
171+
config = com.wireguard.config.Config.parse(inputStream)
172+
futureBackend.await().setState(tun, Tunnel.State.UP, config);
173+
flutterSuccess(result, "")
152174
} catch (e: Throwable) {
153-
Log.e(TAG, "Statistics - Can't get stats: ${e.message}", e)
175+
Log.e(TAG, "Connect - Can't connect to tunnel: $e", e)
176+
status = queryStatus()
154177
flutterError(result, e.message.toString())
155178
}
156179
}
157180
}
158181

159182
private fun disconnect(result: Result) {
160-
183+
val tun = tunnel ?: run {
184+
result.error("err_setup_tunnel", "Tunnel is not initialized", null)
185+
return
186+
}
161187
scope.launch(Dispatchers.IO) {
162188
try {
163189
if (futureBackend.await().runningTunnelNames.isEmpty()) {
164190
throw Exception("Tunnel is not running")
165191
}
166-
167-
futureBackend.await().setState(
168-
tunnel(tunnelName) { state ->
169-
scope.launch(Dispatchers.Main) {
170-
Log.i(TAG, "onStateChange - $state")
171-
channel.invokeMethod(
172-
"onStateChange", state == Tunnel.State.UP
173-
)
174-
}
175-
}, Tunnel.State.DOWN, config
176-
)
177-
Log.i(TAG, "Disconnect - success!")
192+
futureBackend.await().setState(tun, Tunnel.State.DOWN, config)
178193
flutterSuccess(result, "")
179-
} catch (e: BackendException) {
180-
Log.e(TAG, "Disconnect - BackendException - ERROR - ${e.reason}", e)
181-
flutterError(result, e.reason.toString())
182194
} catch (e: Throwable) {
183195
Log.e(TAG, "Disconnect - Can't disconnect from tunnel: ${e.message}")
196+
status = queryStatus()
184197
flutterError(result, e.message.toString())
185198
}
186199
}
187200
}
188201

189-
private fun connect(cfg: String, result: Result) {
190202

203+
private fun statistics(result: Result) {
204+
val tun = tunnel ?: run {
205+
result.error("err_setup_tunnel", "Tunnel is not initialized", null)
206+
return
207+
}
191208
scope.launch(Dispatchers.IO) {
192209
try {
193-
if (!havePermission) {
194-
checkPermission()
195-
throw Exception("Permissions are not given")
196-
}
197-
val inputStream = ByteArrayInputStream(cfg.toByteArray())
198-
config = com.wireguard.config.Config.parse(inputStream)
199-
futureBackend.await().setState(
200-
tunnel(tunnelName) { state ->
201-
scope.launch(Dispatchers.Main) {
202-
Log.i(TAG, "onStateChange - $state")
203-
channel.invokeMethod(
204-
"onStateChange", state == Tunnel.State.UP
205-
)
206-
}
207-
}, Tunnel.State.UP, config
210+
val statistics = futureBackend.await().getStatistics(tun)
211+
val stats = Stats(statistics.totalRx(), statistics.totalTx())
212+
213+
flutterSuccess(
214+
result, Klaxon().toJsonString(
215+
stats
208216
)
209-
Log.i(TAG, "Connect - success!")
210-
flutterSuccess(result, "")
217+
)
218+
Log.i(TAG, "Statistics - ${stats.totalDownload} ${stats.totalUpload}")
219+
211220
} catch (e: BackendException) {
212-
Log.e(TAG, "Connect - BackendException - ERROR - ${e.reason}", e)
221+
Log.e(TAG, "Statistics - BackendException - ERROR - ${e.reason} ", e)
213222
flutterError(result, e.reason.toString())
214223
} catch (e: Throwable) {
215-
Log.e(TAG, "Connect - Can't connect to tunnel: $e", e)
224+
Log.e(TAG, "Statistics - Can't get stats: ${e.message}", e)
216225
flutterError(result, e.message.toString())
217226
}
218227
}
219228
}
220229

221-
private fun setupTunnel(bundleId: String, result: Result) {
222-
scope.launch(Dispatchers.IO) {
223-
if (Tunnel.isNameInvalid(bundleId)) {
224-
flutterError(result, "Invalid Name")
225-
return@launch
226-
}
227-
tunnelName = bundleId
228-
checkPermission()
229-
result.success(null)
230-
}
231-
}
232-
233230
private fun checkPermission() {
234231
val intent = GoBackend.VpnService.prepare(this.activity)
235232
if (intent != null) {
@@ -240,32 +237,31 @@ class WireguardDartPlugin : FlutterPlugin, MethodCallHandler, ActivityAware,
240237
}
241238
}
242239

243-
private fun generateKeyPair(result: Result) {
244-
val keyPair = KeyPair()
245-
val privateKey = keyPair.privateKey.toBase64()
246-
val publicKey = KeyPair(Key.fromBase64(privateKey)).publicKey.toBase64()
247-
val map: HashMap<String, String> =
248-
hashMapOf("privateKey" to privateKey, "publicKey" to publicKey)
249-
result.success(map)
250-
return
251-
}
252-
253240
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
254241
channel.setMethodCallHandler(null)
255242
}
256243

257-
private fun tunnel(name: String, callback: StateChangeCallback? = null): WireguardTunnel {
244+
private fun status(result: Result) {
245+
val status = queryStatus()
246+
result.success(hashMapOf("status" to status.name))
247+
}
248+
249+
private fun queryStatus(): ConnectionStatus {
258250
if (tunnel == null) {
259-
tunnel = WireguardTunnel(name, callback)
251+
return ConnectionStatus.unknown
252+
}
253+
return when (backend?.getState(tunnel!!)) {
254+
Tunnel.State.DOWN -> ConnectionStatus.disconnected
255+
Tunnel.State.UP -> ConnectionStatus.connected
256+
else -> ConnectionStatus.unknown
260257
}
261-
return tunnel as WireguardTunnel
262258
}
263259
}
264260

265261
typealias StateChangeCallback = (Tunnel.State) -> Unit
266262

267263
class WireguardTunnel(
268-
private val name: String, private val onStateChanged: StateChangeCallback? = null
264+
private val name: String, private val onStateChanged: StateChangeCallback? = null
269265
) : Tunnel {
270266

271267
override fun getName() = name
@@ -277,8 +273,8 @@ class WireguardTunnel(
277273
}
278274

279275
class Stats(
280-
val totalDownload: Long,
281-
val totalUpload: Long,
276+
val totalDownload: Long,
277+
val totalUpload: Long,
282278
)
283279

284280

File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)