Skip to content
Snippets Groups Projects
Commit 6d0a6453 authored by Dan Ballard's avatar Dan Ballard
Browse files

fixup! Add Tor integration and UI

Bug 42252: Make TorController and interface and add a Geckoview implementation
parent 47c80363
Branches
Tags
1 merge request!60Bug 42252: Make TorController an Interface, add implementation for GeckoView TorAndroidIntegration
......@@ -43,7 +43,8 @@ import org.mozilla.fenix.perf.StartupActivityLog
import org.mozilla.fenix.perf.StartupStateProvider
import org.mozilla.fenix.perf.StrictModeManager
import org.mozilla.fenix.perf.lazyMonitored
import org.mozilla.fenix.tor.TorController
import org.mozilla.fenix.tor.TorControllerGV
import org.mozilla.fenix.tor.TorControllerTAS
import org.mozilla.fenix.utils.ClipboardHandler
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.wifi.WifiConnectionMonitor
......@@ -201,7 +202,7 @@ class Components(private val context: Context) {
),
)
}
val torController by lazyMonitored { TorController(context) }
val torController by lazyMonitored { if (settings.useNewBootstrap) TorControllerGV(context) else TorControllerTAS(context) }
}
/**
......
......@@ -4,22 +4,7 @@
package org.mozilla.fenix.tor
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeoutOrNull
import org.mozilla.fenix.BuildConfig
import org.torproject.android.service.TorService
import org.torproject.android.service.TorServiceConstants
import org.torproject.android.service.util.Prefs
interface TorEvents {
fun onTorConnecting()
......@@ -28,347 +13,59 @@ interface TorEvents {
fun onTorStopped()
}
private enum class TorStatus {
OFF,
STARTING,
ON,
STOPPING,
UNKNOWN;
internal enum class TorStatus(val status: String) {
OFF("OFF"),
STARTING("STARTING"),
ON("ON"),
STOPPING("STOPPING"),
UNKNOWN("UNKNOWN");
fun getStateFromString(status: String): TorStatus {
companion object {
fun fromString(status: String): TorStatus {
return when (status) {
TorServiceConstants.STATUS_ON -> ON
TorServiceConstants.STATUS_STARTING -> STARTING
TorServiceConstants.STATUS_STOPPING -> STOPPING
TorServiceConstants.STATUS_OFF -> OFF
"ON" -> ON
"STARTING" -> STARTING
"STOPPING" -> STOPPING
"OFF" -> OFF
else -> UNKNOWN
}
}
}
fun isOff() = this == OFF
fun isOn() = this == ON
fun isStarting() = this == STARTING
fun isStarted() = ((this == TorStatus.STARTING) || (this == TorStatus.ON))
fun isStarted() = ((this == STARTING) || (this == ON))
fun isStopping() = this == STOPPING
fun isUnknown() = this == UNKNOWN
}
@SuppressWarnings("TooManyFunctions")
class TorController(
private val context: Context
) : TorEvents {
private val lbm: LocalBroadcastManager = LocalBroadcastManager.getInstance(context)
private val entries = mutableListOf<Pair<String?, String?>>()
val logEntries get() = entries
private var torListeners = mutableListOf<TorEvents>()
private var pendingRegisterChangeList = mutableListOf<Pair<TorEvents, Boolean>>()
private var lockTorListenersMutation = false
private var lastKnownStatus = TorStatus.OFF
private var wasTorBootstrapped = false
private var isTorRestarting = false
// This may be a lie
private var isTorBootstrapped = false
get() = ((lastKnownStatus == TorStatus.ON) && wasTorBootstrapped)
val isDebugLoggingEnabled get() =
context
.getSharedPreferences("org.torproject.android_preferences", Context.MODE_PRIVATE)
.getBoolean("pref_enable_logging", false)
val isStarting get() = lastKnownStatus.isStarting()
val isRestarting get() = isTorRestarting
val isBootstrapped get() = isTorBootstrapped
val isConnected get() = (lastKnownStatus.isStarted() && !isTorRestarting)
interface TorController: TorEvents {
val logEntries: MutableList<Pair<String?, String?>>
val isStarting: Boolean
val isRestarting: Boolean
val isBootstrapped: Boolean
val isConnected: Boolean
var bridgesEnabled: Boolean
get() = Prefs.bridgesEnabled()
set(value) { Prefs.putBridgesEnabled(value) }
var bridgeTransport: TorBridgeTransportConfig
get() {
return TorBridgeTransportConfigUtil.getStringToBridgeTransport(
Prefs.getBridgesList()
)
}
set(value) {
if (value == TorBridgeTransportConfig.USER_PROVIDED) {
// Don't set the pref when the value is USER_PROVIDED because
// "user_provided" is not a valid bridge or transport type.
// This call should be followed by setting userProvidedBridges.
return
}
Prefs.setBridgesList(value.transportName)
}
var userProvidedBridges: String?
get() {
val bridges = Prefs.getBridgesList()
val bridgeType =
TorBridgeTransportConfigUtil.getStringToBridgeTransport(bridges)
return when (bridgeType) {
TorBridgeTransportConfig.USER_PROVIDED -> bridges
else -> null
}
}
set(value) {
Prefs.setBridgesList(value)
}
fun start() {
// Register receiver
lbm.registerReceiver(
persistentBroadcastReceiver,
IntentFilter(TorServiceConstants.ACTION_STATUS)
)
lbm.registerReceiver(
persistentBroadcastReceiver,
IntentFilter(TorServiceConstants.LOCAL_ACTION_LOG)
)
}
fun stop() {
lbm.unregisterReceiver(persistentBroadcastReceiver)
}
private val persistentBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == null ||
(intent.action != TorServiceConstants.ACTION_STATUS &&
intent.action != TorServiceConstants.LOCAL_ACTION_LOG)
) {
return
}
val action = intent.action
val logentry: String?
val status: String?
if (action == TorServiceConstants.LOCAL_ACTION_LOG) {
logentry = intent.getExtras()
?.getCharSequence(TorServiceConstants.LOCAL_EXTRA_LOG) as? String?
} else {
logentry = null
}
status = intent.getExtras()
?.getCharSequence(TorServiceConstants.EXTRA_STATUS) as? String?
if (logentry == null && status == null) {
return
}
onTorStatusUpdate(logentry, status)
if (status == null) {
return
}
val newStatus = lastKnownStatus.getStateFromString(status)
if (newStatus.isUnknown() && wasTorBootstrapped) {
stopTor()
}
entries.add(Pair(logentry, status))
if (logentry != null && logentry.contains(TorServiceConstants.TOR_CONTROL_PORT_MSG_BOOTSTRAP_DONE)) {
wasTorBootstrapped = true
onTorConnected()
}
if (lastKnownStatus.isStopping() && newStatus.isOff()) {
if (isTorRestarting) {
initiateTorBootstrap()
} else {
onTorStopped()
}
}
if (lastKnownStatus.isOff() && newStatus.isStarting()) {
isTorRestarting = false
}
lastKnownStatus = newStatus
}
}
override fun onTorConnecting() {
lockTorListenersMutation = true
torListeners.forEach { it.onTorConnecting() }
lockTorListenersMutation = false
handlePendingRegistrationChanges()
}
override fun onTorConnected() {
lockTorListenersMutation = true
torListeners.forEach { it.onTorConnected() }
lockTorListenersMutation = false
handlePendingRegistrationChanges()
}
override fun onTorStatusUpdate(entry: String?, status: String?) {
lockTorListenersMutation = true
torListeners.forEach { it.onTorStatusUpdate(entry, status) }
lockTorListenersMutation = false
handlePendingRegistrationChanges()
}
override fun onTorStopped() {
lockTorListenersMutation = true
torListeners.forEach { it.onTorStopped() }
lockTorListenersMutation = false
handlePendingRegistrationChanges()
}
fun registerTorListener(l: TorEvents) {
if (torListeners.contains(l)) {
return
}
if (lockTorListenersMutation) {
pendingRegisterChangeList.add(Pair(l, true))
} else {
torListeners.add(l)
}
}
fun unregisterTorListener(l: TorEvents) {
if (!torListeners.contains(l)) {
return
}
fun start()
fun stop()
if (lockTorListenersMutation) {
pendingRegisterChangeList.add(Pair(l, false))
} else {
torListeners.remove(l)
}
}
override fun onTorConnecting()
override fun onTorConnected()
override fun onTorStatusUpdate(entry: String?, status: String?)
override fun onTorStopped()
private fun handlePendingRegistrationChanges() {
pendingRegisterChangeList.forEach {
if (it.second) {
registerTorListener(it.first)
} else {
unregisterTorListener(it.first)
}
}
fun registerTorListener(l: TorEvents)
fun unregisterTorListener(l: TorEvents)
pendingRegisterChangeList.clear()
fun initiateTorBootstrap(lifecycleScope: LifecycleCoroutineScope? = null, withDebugLogging: Boolean = false)
fun stopTor()
fun setTorStopped()
fun restartTor()
}
/**
* Receive the current Tor status.
*
* Send a request for the current status and receive the response.
* Returns true if Tor is running, false otherwise.
*
*/
private suspend fun checkTorIsStarted(): Boolean {
val channel = Channel<Boolean>()
// Register receiver
val lbm: LocalBroadcastManager = LocalBroadcastManager.getInstance(context)
val localBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action ?: return
// We only want ACTION_STATUS messages
if (action != TorServiceConstants.ACTION_STATUS) {
return
}
// The current status has the EXTRA_STATUS key
val currentStatus =
intent.getStringExtra(TorServiceConstants.EXTRA_STATUS)
channel.trySend(currentStatus === TorServiceConstants.STATUS_ON)
}
}
lbm.registerReceiver(
localBroadcastReceiver,
IntentFilter(TorServiceConstants.ACTION_STATUS)
)
// Request service status
sendServiceAction(TorServiceConstants.ACTION_STATUS)
// Wait for response and unregister receiver
var torIsStarted = false
withTimeoutOrNull(torServiceResponseTimeout) {
torIsStarted = channel.receive()
}
lbm.unregisterReceiver(localBroadcastReceiver)
return torIsStarted
}
fun initiateTorBootstrap(lifecycleScope: LifecycleCoroutineScope? = null, withDebugLogging: Boolean = false) {
if (BuildConfig.DISABLE_TOR) {
return
}
context.getSharedPreferences("org.torproject.android_preferences", Context.MODE_PRIVATE)
.edit().putBoolean("pref_enable_logging", withDebugLogging).apply()
if (lifecycleScope == null) {
sendServiceAction(TorServiceConstants.ACTION_START)
} else {
lifecycleScope.launch {
val torNeedsStart = !checkTorIsStarted()
if (torNeedsStart) {
sendServiceAction(TorServiceConstants.ACTION_START)
}
}
}
}
fun stopTor() {
if (BuildConfig.DISABLE_TOR) {
return
}
val torService = Intent(context, TorService::class.java)
context.stopService(torService)
}
fun setTorStopped() {
lastKnownStatus = TorStatus.OFF
onTorStopped()
}
fun restartTor() {
// tor-android-service doesn't dynamically update the torrc file,
// and it doesn't use SETCONF, so we completely restart the service.
// However, don't restart if we aren't started and we weren't
// previously started.
if (!lastKnownStatus.isStarted() && !wasTorBootstrapped) {
return
}
if (!lastKnownStatus.isStarted() && wasTorBootstrapped) {
// If we aren't started, but we were previously bootstrapped,
// then we handle a "restart" request as a "start" restart
initiateTorBootstrap()
} else {
// |isTorRestarting| tracks the state of restart. When we receive an |OFF| state
// from TorService in persistentBroadcastReceiver::onReceive we restart the Tor
// service.
isTorRestarting = true
stopTor()
}
}
private fun sendServiceAction(action: String) {
val torServiceStatus = Intent(context, TorService::class.java)
torServiceStatus.action = action
context.startService(torServiceStatus)
}
companion object {
const val torServiceResponseTimeout = 5000L
}
}
package org.mozilla.fenix.tor
import android.content.Context
import android.util.Log
import androidx.lifecycle.LifecycleCoroutineScope
import mozilla.components.browser.engine.gecko.GeckoEngine
import org.mozilla.fenix.ext.components
import org.mozilla.geckoview.TorIntegrationAndroid
import org.mozilla.geckoview.TorIntegrationAndroid.BootstrapStateChangeListener
import org.mozilla.geckoview.TorSettings
import org.mozilla.geckoview.TorSettings.BridgeBuiltinType
import org.mozilla.geckoview.TorSettings.BridgeSource
// Enum matching TorConnectState from TorConnect.sys.mjs that we get from onBootstrapStateChange
internal enum class TorConnectState(val state: String) {
Initial("Initial"),
Configuring("Configuring"),
AutoBootstrapping("AutoBootstrapping"),
Bootstrapping("Bootstrapping"),
Error("Error"),
Bootstrapped("Bootstrapped"),
Disabled("Disabled");
fun isStarting() = this == Bootstrapping || this == AutoBootstrapping
fun isError() = this == Error
fun isStarted() = this == Bootstrapped
fun isOff() = this == Initial || this == Configuring || this == Disabled || this == Error
// Convert to TorStatus that firefox-android uses based on tor-android-service
fun toTorStatus(): TorStatus {
return when (this) {
Initial -> TorStatus.OFF
Configuring -> TorStatus.OFF
AutoBootstrapping -> TorStatus.STARTING
Bootstrapping -> TorStatus.STARTING
Error -> TorStatus.UNKNOWN
Bootstrapped -> TorStatus.ON
Disabled -> TorStatus.OFF
}
}
}
class TorControllerGV(
private val context: Context,
) : TorController, TorEvents, BootstrapStateChangeListener {
private val TAG = "TorControllerGV"
private var torListeners = mutableListOf<TorEvents>()
private var lastKnownStatus = TorConnectState.Initial
private var wasTorBootstrapped = false
private var isTorRestarting = false
private var isTorBootstrapped = false
get() = ((lastKnownStatus.isStarted()) && wasTorBootstrapped)
private val entries = mutableListOf<Pair<String?, String?>>()
override val logEntries get() = entries
override val isStarting get() = lastKnownStatus.isStarting()
override val isRestarting get() = isTorRestarting
override val isBootstrapped get() = isTorBootstrapped
override val isConnected get() = (lastKnownStatus.isStarted() && !isTorRestarting)
private fun getTorIntegration(): TorIntegrationAndroid {
return (context.components.core.engine as GeckoEngine).getTorIntegrationController()
}
private fun getTorSettings(): TorSettings? {
return getTorIntegration().getSettings()
}
override var bridgesEnabled: Boolean
get() {
return getTorSettings()?.bridgesEnabled ?: false
}
set(value) {
getTorSettings()?.let {
it.bridgesEnabled = value
getTorIntegration().setSettings(it, true, true)
}
}
override var bridgeTransport: TorBridgeTransportConfig
get() {
return when (getTorSettings()?.bridgesSource) {
BridgeSource.BuiltIn -> {
when (getTorSettings()?.bridgesBuiltinType) {
BridgeBuiltinType.Obfs4 -> TorBridgeTransportConfig.BUILTIN_OBFS4
BridgeBuiltinType.MeekAzure -> TorBridgeTransportConfig.BUILTIN_MEEK_AZURE
BridgeBuiltinType.Snowflake -> TorBridgeTransportConfig.BUILTIN_SNOWFLAKE
else -> TorBridgeTransportConfig.USER_PROVIDED
}
}
BridgeSource.UserProvided -> TorBridgeTransportConfig.USER_PROVIDED
else -> TorBridgeTransportConfig.USER_PROVIDED
}
}
set(value) {
getTorSettings()?.let {
if (value == TorBridgeTransportConfig.USER_PROVIDED) {
it.bridgesSource = BridgeSource.BuiltIn
} else {
val bbt: BridgeBuiltinType = when (value) {
TorBridgeTransportConfig.BUILTIN_OBFS4 -> BridgeBuiltinType.Obfs4
TorBridgeTransportConfig.BUILTIN_MEEK_AZURE -> BridgeBuiltinType.MeekAzure
TorBridgeTransportConfig.BUILTIN_SNOWFLAKE -> BridgeBuiltinType.Snowflake
else -> BridgeBuiltinType.Invalid
}
it.bridgesBuiltinType = bbt
}
getTorIntegration().setSettings(it, true, true)
}
}
override var userProvidedBridges: String?
get() {
return getTorSettings()?.bridgeBridgeStrings?.joinToString("\r\n")
}
set(value) {
getTorSettings()?.let {
it.bridgeBridgeStrings = value?.split("\r\n")?.toTypedArray() ?: arrayOf<String>()
getTorIntegration().setSettings(it, true, true)
}
}
override fun start() {
getTorIntegration().registerBootstrapStateChangeListener(this)
}
override fun stop() {
getTorIntegration().unregisterBootstrapStateChangeListener(this)
}
// TorEvents
override fun onTorConnecting() {
synchronized(torListeners) {
torListeners.forEach { it.onTorConnecting() }
}
}
// TorEvents
override fun onTorConnected() {
synchronized(torListeners) {
torListeners.forEach { it.onTorConnected() }
}
}
// TorEvents
override fun onTorStatusUpdate(entry: String?, status: String?) {
synchronized(torListeners) {
torListeners.forEach { it.onTorStatusUpdate(entry, status) }
}
}
// TorEvents
override fun onTorStopped() {
synchronized(torListeners) {
torListeners.forEach { it.onTorStopped() }
}
}
override fun registerTorListener(l: TorEvents) {
synchronized(torListeners) {
if (torListeners.contains(l)) {
return
}
torListeners.add(l)
}
}
override fun unregisterTorListener(l: TorEvents) {
synchronized(torListeners) {
if (!torListeners.contains(l)) {
return
}
torListeners.remove(l)
}
}
override fun initiateTorBootstrap(
lifecycleScope: LifecycleCoroutineScope?,
withDebugLogging: Boolean,
) {
getTorIntegration().beginBootstrap()
}
override fun stopTor() {
getTorIntegration().cancelBootstrap()
}
override fun setTorStopped() {
lastKnownStatus = TorConnectState.Disabled
onTorStopped()
}
override fun restartTor() {
if (!lastKnownStatus.isStarted() && wasTorBootstrapped) {
// If we aren't started, but we were previously bootstrapped,
// then we handle a "restart" request as a "start" restart
initiateTorBootstrap()
} else {
// |isTorRestarting| tracks the state of restart. When we receive an |OFF| state
// from TorService in persistentBroadcastReceiver::onReceive we restart the Tor
// service.
isTorRestarting = true
stopTor()
}
}
// TorEventsBootstrapStateChangeListener -> (lastKnowStatus, TorEvents)
// Handle events from GeckoView TorAndroidIntegration and map to TorEvents based events
// and state for firefox-android (designed for tor-android-service)
// fun onTorConnecting()
// fun onTorConnected()
// fun onTorStatusUpdate(entry: String?, status: String?)
// fun onTorStopped()
// TorEventsBootstrapStateChangeListener
override fun onBootstrapStateChange(newStateVal: String?) {
Log.d(TAG, "onBootstrapStateChange($newStateVal)")
val newState: TorConnectState = TorConnectState.valueOf(newStateVal ?: "Error")
if (newState.isError() && wasTorBootstrapped) {
stopTor()
}
if (newState.isStarted()) {
wasTorBootstrapped = true
onTorConnected()
}
if (wasTorBootstrapped && newState == TorConnectState.Configuring) {
wasTorBootstrapped = false
if (isTorRestarting) {
initiateTorBootstrap()
} else {
onTorStopped()
}
}
if (lastKnownStatus.isOff() && newState.isStarting()) {
isTorRestarting = false
}
lastKnownStatus = newState
}
// TorEventsBootstrapStateChangeListener
override fun onBootstrapProgress(progress: Double, status: String?, hasWarnings: Boolean) {
Log.d(TAG, "onBootstrapProgress($progress, $status, $hasWarnings)")
if (progress == 100.0) {
lastKnownStatus = TorConnectState.Bootstrapped
wasTorBootstrapped = true
onTorConnected()
} else {
lastKnownStatus = TorConnectState.Bootstrapping
onTorConnecting()
}
entries.add(Pair(status, lastKnownStatus.toTorStatus().status))
onTorStatusUpdate(status, lastKnownStatus.toTorStatus().status)
}
// TorEventsBootstrapStateChangeListener
override fun onBootstrapComplete() {
lastKnownStatus = TorConnectState.Bootstrapped
this.onTorConnected()
}
// TorEventsBootstrapStateChangeListener
override fun onBootstrapError(message: String?, details: String?) {
lastKnownStatus = TorConnectState.Error
}
// TorEventsBootstrapStateChangeListener
override fun onSettingsRequested() {
// noop
}
}
package org.mozilla.fenix.tor
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeoutOrNull
import org.mozilla.fenix.BuildConfig
import org.torproject.android.service.TorService
import org.torproject.android.service.TorServiceConstants
import org.torproject.android.service.util.Prefs
@SuppressWarnings("TooManyFunctions")
class TorControllerTAS (private val context: Context): TorController {
private val lbm: LocalBroadcastManager = LocalBroadcastManager.getInstance(context)
private val entries = mutableListOf<Pair<String?, String?>>()
override val logEntries get() = entries
private var torListeners = mutableListOf<TorEvents>()
private var pendingRegisterChangeList = mutableListOf<Pair<TorEvents, Boolean>>()
private var lockTorListenersMutation = false
private var lastKnownStatus = TorStatus.OFF
private var wasTorBootstrapped = false
private var isTorRestarting = false
// This may be a lie
private var isTorBootstrapped = false
get() = ((lastKnownStatus == TorStatus.ON) && wasTorBootstrapped)
val isDebugLoggingEnabled get() =
context
.getSharedPreferences("org.torproject.android_preferences", Context.MODE_PRIVATE)
.getBoolean("pref_enable_logging", false)
override val isStarting get() = lastKnownStatus.isStarting()
override val isRestarting get() = isTorRestarting
override val isBootstrapped get() = isTorBootstrapped
override val isConnected get() = (lastKnownStatus.isStarted() && !isTorRestarting)
override var bridgesEnabled: Boolean
get() = Prefs.bridgesEnabled()
set(value) { Prefs.putBridgesEnabled(value) }
override var bridgeTransport: TorBridgeTransportConfig
get() {
return TorBridgeTransportConfigUtil.getStringToBridgeTransport(
Prefs.getBridgesList()
)
}
set(value) {
if (value == TorBridgeTransportConfig.USER_PROVIDED) {
// Don't set the pref when the value is USER_PROVIDED because
// "user_provided" is not a valid bridge or transport type.
// This call should be followed by setting userProvidedBridges.
return
}
Prefs.setBridgesList(value.transportName)
}
override var userProvidedBridges: String?
get() {
val bridges = Prefs.getBridgesList()
val bridgeType =
TorBridgeTransportConfigUtil.getStringToBridgeTransport(bridges)
return when (bridgeType) {
TorBridgeTransportConfig.USER_PROVIDED -> bridges
else -> null
}
}
set(value) {
Prefs.setBridgesList(value)
}
override fun start() {
// Register receiver
lbm.registerReceiver(
persistentBroadcastReceiver,
IntentFilter(TorServiceConstants.ACTION_STATUS)
)
lbm.registerReceiver(
persistentBroadcastReceiver,
IntentFilter(TorServiceConstants.LOCAL_ACTION_LOG)
)
}
override fun stop() {
lbm.unregisterReceiver(persistentBroadcastReceiver)
}
private val persistentBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == null ||
(intent.action != TorServiceConstants.ACTION_STATUS &&
intent.action != TorServiceConstants.LOCAL_ACTION_LOG)
) {
return
}
val action = intent.action
val logentry: String?
val status: String?
if (action == TorServiceConstants.LOCAL_ACTION_LOG) {
logentry = intent.getExtras()
?.getCharSequence(TorServiceConstants.LOCAL_EXTRA_LOG) as? String?
} else {
logentry = null
}
status = intent.getExtras()
?.getCharSequence(TorServiceConstants.EXTRA_STATUS) as? String?
if (logentry == null && status == null) {
return
}
onTorStatusUpdate(logentry, status)
if (status == null) {
return
}
val newStatus = TorStatus.fromString(status)
if (newStatus.isUnknown() && wasTorBootstrapped) {
stopTor()
}
entries.add(Pair(logentry, status))
if (logentry != null && logentry.contains(TorServiceConstants.TOR_CONTROL_PORT_MSG_BOOTSTRAP_DONE)) {
wasTorBootstrapped = true
onTorConnected()
}
if (lastKnownStatus.isStopping() && newStatus.isOff()) {
if (isTorRestarting) {
initiateTorBootstrap()
} else {
onTorStopped()
}
}
if (lastKnownStatus.isOff() && newStatus.isStarting()) {
isTorRestarting = false
}
lastKnownStatus = newStatus
}
}
override fun onTorConnecting() {
lockTorListenersMutation = true
torListeners.forEach { it.onTorConnecting() }
lockTorListenersMutation = false
handlePendingRegistrationChanges()
}
override fun onTorConnected() {
lockTorListenersMutation = true
torListeners.forEach { it.onTorConnected() }
lockTorListenersMutation = false
handlePendingRegistrationChanges()
}
override fun onTorStatusUpdate(entry: String?, status: String?) {
lockTorListenersMutation = true
torListeners.forEach { it.onTorStatusUpdate(entry, status) }
lockTorListenersMutation = false
handlePendingRegistrationChanges()
}
override fun onTorStopped() {
lockTorListenersMutation = true
torListeners.forEach { it.onTorStopped() }
lockTorListenersMutation = false
handlePendingRegistrationChanges()
}
override fun registerTorListener(l: TorEvents) {
if (torListeners.contains(l)) {
return
}
if (lockTorListenersMutation) {
pendingRegisterChangeList.add(Pair(l, true))
} else {
torListeners.add(l)
}
}
override fun unregisterTorListener(l: TorEvents) {
if (!torListeners.contains(l)) {
return
}
if (lockTorListenersMutation) {
pendingRegisterChangeList.add(Pair(l, false))
} else {
torListeners.remove(l)
}
}
private fun handlePendingRegistrationChanges() {
pendingRegisterChangeList.forEach {
if (it.second) {
registerTorListener(it.first)
} else {
unregisterTorListener(it.first)
}
}
pendingRegisterChangeList.clear()
}
/**
* Receive the current Tor status.
*
* Send a request for the current status and receive the response.
* Returns true if Tor is running, false otherwise.
*
*/
private suspend fun checkTorIsStarted(): Boolean {
val channel = Channel<Boolean>()
// Register receiver
val lbm: LocalBroadcastManager = LocalBroadcastManager.getInstance(context)
val localBroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action ?: return
// We only want ACTION_STATUS messages
if (action != TorServiceConstants.ACTION_STATUS) {
return
}
// The current status has the EXTRA_STATUS key
val currentStatus =
intent.getStringExtra(TorServiceConstants.EXTRA_STATUS)
channel.trySend(currentStatus === TorServiceConstants.STATUS_ON)
}
}
lbm.registerReceiver(
localBroadcastReceiver,
IntentFilter(TorServiceConstants.ACTION_STATUS)
)
// Request service status
sendServiceAction(TorServiceConstants.ACTION_STATUS)
// Wait for response and unregister receiver
var torIsStarted = false
withTimeoutOrNull(torServiceResponseTimeout) {
torIsStarted = channel.receive()
}
lbm.unregisterReceiver(localBroadcastReceiver)
return torIsStarted
}
override fun initiateTorBootstrap(lifecycleScope: LifecycleCoroutineScope?, withDebugLogging: Boolean) {
if (BuildConfig.DISABLE_TOR) {
return
}
context.getSharedPreferences("org.torproject.android_preferences", Context.MODE_PRIVATE)
.edit().putBoolean("pref_enable_logging", withDebugLogging).apply()
if (lifecycleScope == null) {
sendServiceAction(TorServiceConstants.ACTION_START)
} else {
lifecycleScope.launch {
val torNeedsStart = !checkTorIsStarted()
if (torNeedsStart) {
sendServiceAction(TorServiceConstants.ACTION_START)
}
}
}
}
override fun stopTor() {
if (BuildConfig.DISABLE_TOR) {
return
}
val torService = Intent(context, TorService::class.java)
context.stopService(torService)
}
override fun setTorStopped() {
lastKnownStatus = TorStatus.OFF
onTorStopped()
}
override fun restartTor() {
// tor-android-service doesn't dynamically update the torrc file,
// and it doesn't use SETCONF, so we completely restart the service.
// However, don't restart if we aren't started and we weren't
// previously started.
if (!lastKnownStatus.isStarted() && !wasTorBootstrapped) {
return
}
if (!lastKnownStatus.isStarted() && wasTorBootstrapped) {
// If we aren't started, but we were previously bootstrapped,
// then we handle a "restart" request as a "start" restart
initiateTorBootstrap()
} else {
// |isTorRestarting| tracks the state of restart. When we receive an |OFF| state
// from TorService in persistentBroadcastReceiver::onReceive we restart the Tor
// service.
isTorRestarting = true
stopTor()
}
}
private fun sendServiceAction(action: String) {
val torServiceStatus = Intent(context, TorService::class.java)
torServiceStatus.action = action
context.startService(torServiceStatus)
}
companion object {
const val torServiceResponseTimeout = 5000L
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment