Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@ object Globals {
const val logTag = "Passepartout"
const val serviceLogTag = "PassepartoutVpnService"

const val EVENT_BUFFER_CAPACITY = 64
// FIXME: Build bundle dynamically
const val BUNDLE_FILENAME = "bundle.json"
const val CONSTANTS_FILENAME = "constants.json"
const val PROFILES_DIRECTORY = "profiles-v1"
const val PROFILE_LAST_PATH = "tunnel_profile.json"

const val EVENT_BUFFER_CAPACITY = 64
const val EVENT_REPLAY = 64
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,102 +1,40 @@
package com.algoritmico.passepartout

import android.app.Notification
import android.content.Intent
import android.net.VpnService
import android.os.IBinder
import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import android.util.AtomicFile
import com.algoritmico.passepartout.abi.PassepartoutWrapper
import io.partout.PartoutVpnServiceRuntime
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File

class PassepartoutVpnService: VpnService() {
private val runtime by lazy {
PartoutVpnServiceRuntime(
logTag = Globals.serviceLogTag,
service = this,
engine = VpnEngine(
library = PassepartoutWrapper(),
bundleProvider = {
readAsset(BUNDLE_FILENAME)
},
constantsProvider = {
readAsset(CONSTANTS_FILENAME)
},
cachePathProvider = {
cacheDir.absolutePath
}
),
stopService = {
stopSelf()
}
engine = engine
)
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return runtime.onStartCommand(intent, flags, startId)
}

override fun onDestroy() {
runtime.onDestroy()
stopForeground(STOP_FOREGROUND_REMOVE)
super.onDestroy()
}

override fun onRevoke() {
runtime.onRevoke()
}

override fun onBind(intent: Intent?): IBinder? {
if (intent?.action == SERVICE_INTERFACE) {
return super.onBind(intent)
}
return runtime.onBind(intent)
}

private fun createNotification(): Notification {
val channelId = NOTIFICATION_CHANNEL_ID

// Create a notification channel (required on Android 8.0+)
val channel = NotificationChannelCompat.Builder(
channelId,
NotificationManagerCompat.IMPORTANCE_LOW // low importance to avoid sound
)
.setName("Passepartout VPN")
.setDescription("Notification for the VPN foreground service")
.build()

NotificationManagerCompat
.from(this)
.createNotificationChannel(channel)
private val engine = object : PartoutVpnServiceRuntime.Engine {
private val library = PassepartoutWrapper()
private val lastProfileFile: File
get() = File(noBackupFilesDir, Globals.PROFILE_LAST_PATH)

// Build the notification
return NotificationCompat.Builder(this, channelId)
.setContentTitle("Passepartout Active")
.setContentText("VPN is running")
.setOngoing(true)
.build()
}

private class VpnEngine(
private val library: PassepartoutWrapper,
private val bundleProvider: suspend () -> String,
private val constantsProvider: suspend () -> String,
private val cachePathProvider: () -> String
) : PartoutVpnServiceRuntime.Engine {
override suspend fun start(
runtime: PartoutVpnServiceRuntime,
profileJSON: String
): PartoutVpnServiceRuntime.Result = withContext(Dispatchers.IO) {
PartoutVpnServiceRuntime.Result(
library.tunnelStart(
bundleProvider(),
constantsProvider(),
readAsset(Globals.BUNDLE_FILENAME),
readAsset(Globals.CONSTANTS_FILENAME),
profileJSON,
cachePathProvider(),
cacheDir.absolutePath,
runtime
),
null
Expand All @@ -110,15 +48,48 @@ class PassepartoutVpnService: VpnService() {
}
result.await()
}

override suspend fun readLastProfile(): String {
return withContext(Dispatchers.IO) {
AtomicFile(lastProfileFile).openRead().bufferedReader(Charsets.UTF_8).use {
it.readText()
}
}
}

override suspend fun writeLastProfile(json: String) {
withContext(Dispatchers.IO) {
val file = AtomicFile(lastProfileFile)
val stream = file.startWrite()
try {
stream.write(json.toByteArray(Charsets.UTF_8))
file.finishWrite(stream)
} catch (e: Exception) {
file.failWrite(stream)
throw e
}
}
}
}

companion object {
private const val BUNDLE_FILENAME = "bundle.json"
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return runtime.onStartCommand(intent, flags, startId)
}

private const val CONSTANTS_FILENAME = "constants.json"
override fun onDestroy() {
runtime.onDestroy()
stopForeground(STOP_FOREGROUND_REMOVE)
super.onDestroy()
}

private const val NOTIFICATION_ID = 1
override fun onRevoke() {
runtime.onRevoke()
}

private const val NOTIFICATION_CHANNEL_ID = "vpn_service_channel"
override fun onBind(intent: Intent?): IBinder? {
if (intent?.action == SERVICE_INTERFACE) {
return super.onBind(intent)
}
return runtime.onBind(intent)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import android.content.Context
import android.content.Intent
import android.util.Log
import com.algoritmico.passepartout.Globals
import com.algoritmico.passepartout.PassepartoutVpnService
import com.algoritmico.passepartout.abi.AppABIProfile
import com.algoritmico.passepartout.abi.PassepartoutWrapper
import com.algoritmico.passepartout.abi.helpers.ABIEventDispatcher
import com.algoritmico.passepartout.abi.models.Event
import com.algoritmico.passepartout.readAsset
import com.algoritmico.passepartout.PassepartoutVpnService
import io.partout.PartoutTunnel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow
Expand Down Expand Up @@ -46,9 +46,9 @@ class AppContext(
Log.i(Globals.logTag, ">>> Partout $partoutVersion")
Log.e(Globals.logTag, ">>> Started app")

val bundle = applicationContext.readAsset(BUNDLE_FILENAME)
val constants = applicationContext.readAsset(CONSTANTS_FILENAME)
val profilesDirectory = File(applicationContext.noBackupFilesDir, PROFILES_DIRECTORY)
val bundle = applicationContext.readAsset(Globals.BUNDLE_FILENAME)
val constants = applicationContext.readAsset(Globals.CONSTANTS_FILENAME)
val profilesDirectory = File(applicationContext.noBackupFilesDir, Globals.PROFILES_DIRECTORY)
.apply {
mkdirs()
}
Expand Down Expand Up @@ -101,12 +101,4 @@ class AppContext(
tunnelObservable.close()
library.appDeinit { _, _ -> }
}

companion object {
private const val BUNDLE_FILENAME = "bundle.json"

private const val CONSTANTS_FILENAME = "constants.json"

private const val PROFILES_DIRECTORY = "profiles-v1"
}
}