diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index f96c61588..87c5a2333 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -58,8 +58,8 @@ import org.meshtastic.core.model.util.toChannelSet import org.meshtastic.core.service.IMeshService import org.meshtastic.core.service.MeshServiceNotifications import org.meshtastic.core.service.ServiceRepository -import org.meshtastic.core.service.TakLockState -import org.meshtastic.core.service.TakTokenInfo +import org.meshtastic.core.service.LockdownState +import org.meshtastic.core.service.LockdownTokenInfo import org.meshtastic.core.service.TracerouteResponse import org.meshtastic.core.strings.Res import org.meshtastic.core.strings.client_notification @@ -132,19 +132,19 @@ constructor( meshServiceNotifications.clearClientNotification(notification) } - val takLockState: StateFlow = serviceRepository.takLockState - val takTokenInfo: StateFlow = serviceRepository.takTokenInfo + val lockdownState: StateFlow = serviceRepository.lockdownState + val lockdownTokenInfo: StateFlow = serviceRepository.lockdownTokenInfo - fun sendTakUnlock(passphrase: String, bootTtl: Int = DEFAULT_BOOT_TTL, hourTtl: Int = 0) { - serviceRepository.meshService?.sendTakUnlock(passphrase, bootTtl, hourTtl) + fun sendLockdownUnlock(passphrase: String, bootTtl: Int = DEFAULT_BOOT_TTL, hourTtl: Int = 0) { + serviceRepository.meshService?.sendLockdownUnlock(passphrase, bootTtl, hourTtl) } - fun sendTakLockNow() { - serviceRepository.meshService?.sendTakLockNow() + fun sendLockNow() { + serviceRepository.meshService?.sendLockNow() } - fun clearTakLockState() { - serviceRepository.clearTakLockState() + fun clearLockdownState() { + serviceRepository.clearLockdownState() } /** diff --git a/app/src/main/java/com/geeksville/mesh/service/FromRadioPacketHandler.kt b/app/src/main/java/com/geeksville/mesh/service/FromRadioPacketHandler.kt index 085f2cac0..974a675f2 100644 --- a/app/src/main/java/com/geeksville/mesh/service/FromRadioPacketHandler.kt +++ b/app/src/main/java/com/geeksville/mesh/service/FromRadioPacketHandler.kt @@ -59,7 +59,7 @@ constructor( } configCompleteId != null -> { router.configFlowManager.handleConfigComplete(configCompleteId) - router.takLockHandler.onConfigComplete() + router.lockdownHandler.onConfigComplete() } mqttProxyMessage != null -> mqttManager.handleMqttProxyMessage(mqttProxyMessage) queueStatus != null -> packetHandler.handleQueueStatus(queueStatus) @@ -69,7 +69,7 @@ constructor( clientNotification != null -> { val msg = clientNotification.message if (msg.startsWith("TAK_")) { - router.takLockHandler.handleTakNotification(msg) + router.lockdownHandler.handleLockdownNotification(msg) } else { serviceRepository.setClientNotification(clientNotification) serviceNotifications.showClientNotification(clientNotification) diff --git a/app/src/main/java/com/geeksville/mesh/service/TakLockHandler.kt b/app/src/main/java/com/geeksville/mesh/service/LockdownHandler.kt similarity index 74% rename from app/src/main/java/com/geeksville/mesh/service/TakLockHandler.kt rename to app/src/main/java/com/geeksville/mesh/service/LockdownHandler.kt index 692aeaeb6..caf7904de 100644 --- a/app/src/main/java/com/geeksville/mesh/service/TakLockHandler.kt +++ b/app/src/main/java/com/geeksville/mesh/service/LockdownHandler.kt @@ -19,24 +19,24 @@ package com.geeksville.mesh.service import co.touchlab.kermit.Logger import com.geeksville.mesh.repository.radio.RadioInterfaceService import dagger.Lazy +import org.meshtastic.core.service.LockdownState +import org.meshtastic.core.service.LockdownTokenInfo import org.meshtastic.core.service.ServiceRepository -import org.meshtastic.core.service.TakLockState -import org.meshtastic.core.service.TakTokenInfo import javax.inject.Inject import javax.inject.Singleton @Singleton -class TakLockHandler @Inject constructor( +class LockdownHandler @Inject constructor( private val serviceRepository: ServiceRepository, private val commandSender: MeshCommandSender, - private val passphraseStore: TakPassphraseStore, + private val passphraseStore: LockdownPassphraseStore, private val radioInterfaceService: RadioInterfaceService, private val connectionManager: Lazy, ) { @Volatile private var wasAutoAttempt = false @Volatile private var pendingPassphrase: String? = null - @Volatile private var pendingBoots: Int = TakPassphraseStore.DEFAULT_BOOTS + @Volatile private var pendingBoots: Int = LockdownPassphraseStore.DEFAULT_BOOTS @Volatile private var pendingHours: Int = 0 /** Called when the BLE connection is established, before the first config request. */ @@ -44,15 +44,15 @@ class TakLockHandler @Inject constructor( serviceRepository.setSessionAuthorized(false) wasAutoAttempt = false pendingPassphrase = null - pendingBoots = TakPassphraseStore.DEFAULT_BOOTS + pendingBoots = LockdownPassphraseStore.DEFAULT_BOOTS pendingHours = 0 } /** Called when the BLE connection is lost. */ fun onDisconnect() { serviceRepository.setSessionAuthorized(false) - serviceRepository.setTakTokenInfo(null) - serviceRepository.setTakLockState(TakLockState.None) + serviceRepository.setLockdownTokenInfo(null) + serviceRepository.setLockdownState(LockdownState.None) wasAutoAttempt = false pendingPassphrase = null } @@ -60,7 +60,7 @@ class TakLockHandler @Inject constructor( /** * Called on every config_complete_id. Once [sessionAuthorized] is true (set on TAK_UNLOCKED), * this is a no-op — preventing the startConfigOnly config_complete_id from triggering any - * further TAK handling. The dialog state is driven entirely by clientNotifications. + * further lockdown handling. The dialog state is driven entirely by clientNotifications. */ fun onConfigComplete() { // Session already authenticated — this config_complete_id is from the startConfigOnly() @@ -69,13 +69,13 @@ class TakLockHandler @Inject constructor( } /** - * Routes incoming TAK clientNotification messages: + * Routes incoming lockdown clientNotification messages: * - TAK_NEEDS_PROVISION → device has no passphrase → show "Set Passphrase" dialog * - TAK_LOCKED: → device is locked → auto-unlock with stored passphrase or show dialog * - TAK_UNLOCKED → accepted; save passphrase, authorize session, re-sync config * - TAK_UNLOCK_FAILED → wrong passphrase; clear stored or increment retry counter */ - fun handleTakNotification(message: String?) { + fun handleLockdownNotification(message: String?) { when { message == TAK_NEEDS_PROVISION -> handleNeedsProvision() // Exact "TAK_LOCKED" = Lock Now was acknowledged by the device → re-lock the session. @@ -88,9 +88,9 @@ class TakLockHandler @Inject constructor( } private fun handleLockNowAcknowledged() { - Logger.i { "TAK: Lock Now acknowledged — resetting session authorization" } + Logger.i { "Lockdown: Lock Now acknowledged — resetting session authorization" } serviceRepository.setSessionAuthorized(false) - // Do NOT clear takTokenInfo here — keep it so the dialog pre-fills with the last-known + // Do NOT clear lockdownTokenInfo here — keep it so the dialog pre-fills with the last-known // TTL values. It is refreshed by the next TAK_UNLOCKED response. wasAutoAttempt = false pendingPassphrase = null @@ -98,7 +98,7 @@ class TakLockHandler @Inject constructor( // The fresh config is loaded in handleUnlocked() after successful re-authentication. connectionManager.get().clearRadioConfig() // Signal the UI to disconnect — no dialog, just drop the connection. - serviceRepository.setTakLockState(TakLockState.LockNowAcknowledged) + serviceRepository.setLockdownState(LockdownState.LockNowAcknowledged) } private fun handleLocked() { @@ -106,17 +106,17 @@ class TakLockHandler @Inject constructor( if (deviceAddress != null) { val stored = passphraseStore.getPassphrase(deviceAddress) if (stored != null) { - Logger.i { "TAK: Auto-unlocking (TAK_LOCKED) with stored passphrase for $deviceAddress" } + Logger.i { "Lockdown: Auto-unlocking (TAK_LOCKED) with stored passphrase for $deviceAddress" } wasAutoAttempt = true - commandSender.sendTakPassphrase(stored.passphrase, stored.boots, stored.hours) + commandSender.sendLockdownPassphrase(stored.passphrase, stored.boots, stored.hours) return } } - serviceRepository.setTakLockState(TakLockState.Locked) + serviceRepository.setLockdownState(LockdownState.Locked) } private fun handleNeedsProvision() { - serviceRepository.setTakLockState(TakLockState.NeedsProvision) + serviceRepository.setLockdownState(LockdownState.NeedsProvision) } private fun handleUnlocked(message: String) { @@ -124,11 +124,11 @@ class TakLockHandler @Inject constructor( val passphrase = pendingPassphrase if (deviceAddress != null && passphrase != null) { passphraseStore.savePassphrase(deviceAddress, passphrase, pendingBoots, pendingHours) - Logger.i { "TAK: Saved passphrase for $deviceAddress" } + Logger.i { "Lockdown: Saved passphrase for $deviceAddress" } } pendingPassphrase = null - serviceRepository.setTakTokenInfo(parseTokenInfo(message)) - serviceRepository.setTakLockState(TakLockState.Unlocked) + serviceRepository.setLockdownTokenInfo(parseTokenInfo(message)) + serviceRepository.setLockdownState(LockdownState.Unlocked) // Mark session authorized BEFORE calling startConfigOnly(). When the resulting // config_complete_id arrives, onConfigComplete() will see sessionAuthorized=true and return // immediately — no passphrase re-send, no loop. @@ -137,7 +137,7 @@ class TakLockHandler @Inject constructor( } /** Parses boots= and until= fields from TAK_UNLOCKED:boots=N:until=EPOCH: */ - private fun parseTokenInfo(message: String): TakTokenInfo? { + private fun parseTokenInfo(message: String): LockdownTokenInfo? { var boots = -1 var until = 0L for (segment in message.split(":")) { @@ -146,7 +146,7 @@ class TakLockHandler @Inject constructor( segment.startsWith("until=") -> until = segment.removePrefix("until=").toLongOrNull() ?: 0L } } - return if (boots >= 0) TakTokenInfo(boots, until) else null + return if (boots >= 0) LockdownTokenInfo(boots, until) else null } private fun handleUnlockFailed(message: String) { @@ -159,25 +159,25 @@ class TakLockHandler @Inject constructor( wasAutoAttempt = false if (backoffSeconds != null && backoffSeconds > 0) { // Rate-limited — stored passphrase may still be correct; keep it and show countdown. - Logger.i { "TAK: Auto-unlock rate-limited (backoff=${backoffSeconds}s)" } - serviceRepository.setTakLockState(TakLockState.UnlockBackoff(backoffSeconds)) + Logger.i { "Lockdown: Auto-unlock rate-limited (backoff=${backoffSeconds}s)" } + serviceRepository.setLockdownState(LockdownState.UnlockBackoff(backoffSeconds)) } else { // Wrong passphrase — clear stored passphrase. val deviceAddress = radioInterfaceService.getDeviceAddress() if (deviceAddress != null) { passphraseStore.clearPassphrase(deviceAddress) - Logger.i { "TAK: Auto-unlock failed (wrong passphrase), cleared stored passphrase for $deviceAddress" } + Logger.i { "Lockdown: Auto-unlock failed (wrong passphrase), cleared stored passphrase for $deviceAddress" } } - serviceRepository.setTakLockState(TakLockState.Locked) + serviceRepository.setLockdownState(LockdownState.Locked) } return } // Manual attempt. if (backoffSeconds != null && backoffSeconds > 0) { - Logger.i { "TAK: Unlock failed with backoff of ${backoffSeconds}s" } - serviceRepository.setTakLockState(TakLockState.UnlockBackoff(backoffSeconds)) + Logger.i { "Lockdown: Unlock failed with backoff of ${backoffSeconds}s" } + serviceRepository.setLockdownState(LockdownState.UnlockBackoff(backoffSeconds)) } else { - serviceRepository.setTakLockState(TakLockState.UnlockFailed) + serviceRepository.setLockdownState(LockdownState.UnlockFailed) } } @@ -186,12 +186,12 @@ class TakLockHandler @Inject constructor( pendingBoots = boots pendingHours = hours wasAutoAttempt = false - serviceRepository.setTakLockState(TakLockState.None) // hide dialog while awaiting response - commandSender.sendTakPassphrase(passphrase, boots, hours) + serviceRepository.setLockdownState(LockdownState.None) // hide dialog while awaiting response + commandSender.sendLockdownPassphrase(passphrase, boots, hours) } fun lockNow() { - commandSender.sendTakLockNow() + commandSender.sendLockNow() } companion object { diff --git a/app/src/main/java/com/geeksville/mesh/service/TakPassphraseStore.kt b/app/src/main/java/com/geeksville/mesh/service/LockdownPassphraseStore.kt similarity index 94% rename from app/src/main/java/com/geeksville/mesh/service/TakPassphraseStore.kt rename to app/src/main/java/com/geeksville/mesh/service/LockdownPassphraseStore.kt index 09592007f..87bbeef7e 100644 --- a/app/src/main/java/com/geeksville/mesh/service/TakPassphraseStore.kt +++ b/app/src/main/java/com/geeksville/mesh/service/LockdownPassphraseStore.kt @@ -30,7 +30,7 @@ data class StoredPassphrase( ) @Singleton -class TakPassphraseStore @Inject constructor(app: Application) { +class LockdownPassphraseStore @Inject constructor(app: Application) { private val prefs: SharedPreferences by lazy { val masterKey = MasterKey.Builder(app) @@ -74,7 +74,7 @@ class TakPassphraseStore @Inject constructor(app: Application) { private fun sanitizeKey(address: String): String = address.replace(":", "_") companion object { - private const val PREFS_FILE_NAME = "tak_passphrase_store" + private const val PREFS_FILE_NAME = "lockdown_passphrase_store" const val DEFAULT_BOOTS = 50 } } diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshActionHandler.kt b/app/src/main/java/com/geeksville/mesh/service/MeshActionHandler.kt index 19dabf4ca..28fa58c31 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshActionHandler.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshActionHandler.kt @@ -59,7 +59,7 @@ constructor( private val databaseManager: DatabaseManager, private val serviceNotifications: MeshServiceNotifications, private val messageProcessor: Lazy, - private val takLockHandler: TakLockHandler, + private val lockdownHandler: LockdownHandler, ) { private var scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) @@ -331,12 +331,12 @@ constructor( } } - fun handleSendTakUnlock(passphrase: String, bootTtl: Int, hourTtl: Int) { - takLockHandler.submitPassphrase(passphrase, bootTtl, hourTtl) + fun handleSendLockdownUnlock(passphrase: String, bootTtl: Int, hourTtl: Int) { + lockdownHandler.submitPassphrase(passphrase, bootTtl, hourTtl) } - fun handleSendTakLockNow() { - takLockHandler.lockNow() + fun handleSendLockNow() { + lockdownHandler.lockNow() } fun handleUpdateLastAddress(deviceAddr: String?) { diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshCommandSender.kt b/app/src/main/java/com/geeksville/mesh/service/MeshCommandSender.kt index bf45cd76a..9d4e62abd 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshCommandSender.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshCommandSender.kt @@ -469,7 +469,7 @@ constructor( ), ) - fun sendTakPassphrase(passphrase: String, boots: Int = 0, hours: Int = 0) { + fun sendLockdownPassphrase(passphrase: String, boots: Int = 0, hours: Int = 0) { val myNum = nodeManager?.myNodeNum ?: return // The firmware expects slot 2 as an absolute Unix epoch (seconds), not a duration. // Convert hours duration → absolute epoch; 0 hours means no time-based expiry (until=0). @@ -509,7 +509,7 @@ constructor( packetHandler?.sendToRadio(ToRadio(packet = packet)) } - fun sendTakLockNow() { + fun sendLockNow() { val myNum = nodeManager?.myNodeNum ?: return val securityConfig = Config.SecurityConfig( private_key = ByteString.of(TAK_LOCK_BYTE), diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshConnectionManager.kt b/app/src/main/java/com/geeksville/mesh/service/MeshConnectionManager.kt index 1f945e0de..b98415ef5 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshConnectionManager.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshConnectionManager.kt @@ -70,7 +70,7 @@ constructor( private val commandSender: MeshCommandSender, private val nodeManager: MeshNodeManager, private val analytics: PlatformAnalytics, - private val takLockHandler: Lazy, + private val lockdownHandler: Lazy, ) { private var scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) private var sleepTimeout: Job? = null @@ -144,7 +144,7 @@ constructor( Logger.d { "Starting connect" } connectTimeMsec = System.currentTimeMillis() scope.handledLaunch { nodeRepository.clearMyNodeInfo() } - takLockHandler.get().onConnect() + lockdownHandler.get().onConnect() startConfigOnly() } @@ -183,7 +183,7 @@ constructor( private fun handleDisconnected() { connectionStateHolder.setState(ConnectionState.Disconnected) - takLockHandler.get().onDisconnect() + lockdownHandler.get().onDisconnect() packetHandler.stopPacketQueue() locationManager.stop() mqttManager.stop() diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshRouter.kt b/app/src/main/java/com/geeksville/mesh/service/MeshRouter.kt index 3673bd4cb..4e6280224 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshRouter.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshRouter.kt @@ -36,7 +36,7 @@ constructor( val configFlowManager: MeshConfigFlowManager, val mqttManager: MeshMqttManager, val actionHandler: MeshActionHandler, - val takLockHandler: TakLockHandler, + val lockdownHandler: LockdownHandler, ) { fun start(scope: CoroutineScope) { dataHandler.start(scope) diff --git a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt index c753b24f8..7c7cf262b 100644 --- a/app/src/main/java/com/geeksville/mesh/service/MeshService.kt +++ b/app/src/main/java/com/geeksville/mesh/service/MeshService.kt @@ -379,12 +379,12 @@ class MeshService : Service() { router.actionHandler.handleRequestRebootOta(requestId, destNum, mode, hash) } - override fun sendTakUnlock(passphrase: String, bootTtl: Int, hourTtl: Int) = toRemoteExceptions { - router.actionHandler.handleSendTakUnlock(passphrase, bootTtl, hourTtl) + override fun sendLockdownUnlock(passphrase: String, bootTtl: Int, hourTtl: Int) = toRemoteExceptions { + router.actionHandler.handleSendLockdownUnlock(passphrase, bootTtl, hourTtl) } - override fun sendTakLockNow() = toRemoteExceptions { - router.actionHandler.handleSendTakLockNow() + override fun sendLockNow() = toRemoteExceptions { + router.actionHandler.handleSendLockNow() } } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/TakUnlockDialog.kt b/app/src/main/java/com/geeksville/mesh/ui/LockdownUnlockDialog.kt similarity index 87% rename from app/src/main/java/com/geeksville/mesh/ui/TakUnlockDialog.kt rename to app/src/main/java/com/geeksville/mesh/ui/LockdownUnlockDialog.kt index 3a38022fe..4d13fa154 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/TakUnlockDialog.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/LockdownUnlockDialog.kt @@ -46,22 +46,22 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp -import org.meshtastic.core.service.TakLockState -import org.meshtastic.core.service.TakTokenInfo +import org.meshtastic.core.service.LockdownState +import org.meshtastic.core.service.LockdownTokenInfo @Suppress("LongMethod") @Composable -fun TakUnlockDialog( - takLockState: TakLockState, - takTokenInfo: TakTokenInfo? = null, +fun LockdownUnlockDialog( + lockdownState: LockdownState, + lockdownTokenInfo: LockdownTokenInfo? = null, onSubmit: (passphrase: String, boots: Int, hours: Int) -> Unit, onDismiss: () -> Unit, ) { - val shouldShow = when (takLockState) { - is TakLockState.Locked -> true - is TakLockState.NeedsProvision -> true - is TakLockState.UnlockFailed -> true - is TakLockState.UnlockBackoff -> true + val shouldShow = when (lockdownState) { + is LockdownState.Locked -> true + is LockdownState.NeedsProvision -> true + is LockdownState.UnlockFailed -> true + is LockdownState.UnlockBackoff -> true else -> false } BackHandler(enabled = shouldShow, onBack = onDismiss) @@ -70,9 +70,9 @@ fun TakUnlockDialog( var passphrase by rememberSaveable { mutableStateOf("") } var passwordVisible by rememberSaveable { mutableStateOf(false) } // Pre-fill from most recent TAK_UNLOCKED token info when available. - val initialBoots = takTokenInfo?.bootsRemaining ?: DEFAULT_BOOTS - val initialHours = if ((takTokenInfo?.expiryEpoch ?: 0L) > 0L) { - ((takTokenInfo!!.expiryEpoch - System.currentTimeMillis() / 1000) / 3600) + val initialBoots = lockdownTokenInfo?.bootsRemaining ?: DEFAULT_BOOTS + val initialHours = if ((lockdownTokenInfo?.expiryEpoch ?: 0L) > 0L) { + ((lockdownTokenInfo!!.expiryEpoch - System.currentTimeMillis() / 1000) / 3600) .toInt().coerceAtLeast(0) } else { 0 @@ -80,7 +80,7 @@ fun TakUnlockDialog( var boots by rememberSaveable { mutableIntStateOf(initialBoots) } var hours by rememberSaveable { mutableIntStateOf(initialHours) } - val isProvisioning = takLockState is TakLockState.NeedsProvision + val isProvisioning = lockdownState is LockdownState.NeedsProvision val title = if (isProvisioning) "Set Passphrase" else "Enter Passphrase" val isValid = passphrase.isNotEmpty() && passphrase.length <= MAX_PASSPHRASE_LEN @@ -89,17 +89,17 @@ fun TakUnlockDialog( title = { Text(text = title) }, text = { Column { - when (takLockState) { - is TakLockState.UnlockFailed -> { + when (lockdownState) { + is LockdownState.UnlockFailed -> { Text( text = "Incorrect passphrase.", color = MaterialTheme.colorScheme.error, ) Spacer(modifier = Modifier.height(SPACING_DP.dp)) } - is TakLockState.UnlockBackoff -> { + is LockdownState.UnlockBackoff -> { Text( - text = "Try again in ${takLockState.backoffSeconds} seconds.", + text = "Try again in ${lockdownState.backoffSeconds} seconds.", color = MaterialTheme.colorScheme.error, ) Spacer(modifier = Modifier.height(SPACING_DP.dp)) diff --git a/app/src/main/java/com/geeksville/mesh/ui/Main.kt b/app/src/main/java/com/geeksville/mesh/ui/Main.kt index f2a9c43ae..4721df6be 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Main.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Main.kt @@ -113,7 +113,7 @@ import org.meshtastic.core.navigation.NodesRoutes import org.meshtastic.core.navigation.Route import org.meshtastic.core.navigation.SettingsRoutes import org.meshtastic.core.service.ConnectionState -import org.meshtastic.core.service.TakLockState +import org.meshtastic.core.service.LockdownState import org.meshtastic.core.strings.Res import org.meshtastic.core.strings.app_too_old import org.meshtastic.core.strings.bottom_nav_settings @@ -218,11 +218,11 @@ fun MainScreen(uIViewModel: UIViewModel = hiltViewModel(), scanModel: BTScanMode } } - val takLockState by uIViewModel.takLockState.collectAsStateWithLifecycle() - val takTokenInfo by uIViewModel.takTokenInfo.collectAsStateWithLifecycle() - LaunchedEffect(takLockState) { - if (takLockState is TakLockState.LockNowAcknowledged) { - uIViewModel.clearTakLockState() + val lockdownState by uIViewModel.lockdownState.collectAsStateWithLifecycle() + val lockdownTokenInfo by uIViewModel.lockdownTokenInfo.collectAsStateWithLifecycle() + LaunchedEffect(lockdownState) { + if (lockdownState is LockdownState.LockNowAcknowledged) { + uIViewModel.clearLockdownState() scanModel.disconnect() navController.navigate(TopLevelDestination.Connections.route) { popUpTo(navController.graph.findStartDestination().id) { saveState = true } @@ -231,12 +231,12 @@ fun MainScreen(uIViewModel: UIViewModel = hiltViewModel(), scanModel: BTScanMode } } } - TakUnlockDialog( - takLockState = takLockState, - takTokenInfo = takTokenInfo, - onSubmit = { pass, boots, hours -> uIViewModel.sendTakUnlock(pass, boots, hours) }, + LockdownUnlockDialog( + lockdownState = lockdownState, + lockdownTokenInfo = lockdownTokenInfo, + onSubmit = { pass, boots, hours -> uIViewModel.sendLockdownUnlock(pass, boots, hours) }, onDismiss = { - uIViewModel.clearTakLockState() + uIViewModel.clearLockdownState() scanModel.disconnect() navController.navigate(TopLevelDestination.Connections.route) { popUpTo(navController.graph.findStartDestination().id) { saveState = true } diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsScreen.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsScreen.kt index de4a9f25c..d132cdfd6 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsScreen.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsScreen.kt @@ -70,7 +70,7 @@ import org.jetbrains.compose.resources.stringResource import org.meshtastic.core.navigation.Route import org.meshtastic.core.navigation.SettingsRoutes import org.meshtastic.core.service.ConnectionState -import org.meshtastic.core.service.TakLockState +import org.meshtastic.core.service.LockdownState import org.meshtastic.core.strings.Res import org.meshtastic.core.strings.connected import org.meshtastic.core.strings.connected_device @@ -126,10 +126,10 @@ fun ConnectionsScreen( val ourNode by connectionsViewModel.ourNodeInfo.collectAsStateWithLifecycle() val selectedDevice by scanModel.selectedNotNullFlow.collectAsStateWithLifecycle() val bluetoothState by connectionsViewModel.bluetoothState.collectAsStateWithLifecycle() - val takLockState by connectionsViewModel.takLockState.collectAsStateWithLifecycle() - // A TAK-locked device sends zeroed config before auth — suppress region-unset until authorized. - val isTakAuthorized = takLockState == TakLockState.None || takLockState == TakLockState.Unlocked - val regionUnset = config.lora?.region == Config.LoRaConfig.RegionCode.UNSET && isTakAuthorized + val lockdownState by connectionsViewModel.lockdownState.collectAsStateWithLifecycle() + // A lockdown-mode device sends zeroed config before auth — suppress region-unset until authorized. + val isLockdownAuthorized = lockdownState == LockdownState.None || lockdownState == LockdownState.Unlocked + val regionUnset = config.lora?.region == Config.LoRaConfig.RegionCode.UNSET && isLockdownAuthorized val bleDevices by scanModel.bleDevicesForUi.collectAsStateWithLifecycle() val discoveredTcpDevices by scanModel.discoveredTcpDevicesForUi.collectAsStateWithLifecycle() diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsViewModel.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsViewModel.kt index 42b5e07cd..ab8af3980 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/connections/ConnectionsViewModel.kt @@ -48,7 +48,7 @@ constructor( val connectionState = serviceRepository.connectionState - val takLockState = serviceRepository.takLockState + val lockdownState = serviceRepository.lockdownState val myNodeInfo: StateFlow = nodeRepository.myNodeInfo diff --git a/core/api/src/main/aidl/org/meshtastic/core/service/IMeshService.aidl b/core/api/src/main/aidl/org/meshtastic/core/service/IMeshService.aidl index 0f7532822..750bb7c84 100644 --- a/core/api/src/main/aidl/org/meshtastic/core/service/IMeshService.aidl +++ b/core/api/src/main/aidl/org/meshtastic/core/service/IMeshService.aidl @@ -190,9 +190,9 @@ interface IMeshService { */ void requestRebootOta(in int requestId, in int destNum, in int mode, in byte []hash); - /// Send TAK unlock passphrase to the device - void sendTakUnlock(in String passphrase, in int bootTtl, in int hourTtl); + /// Send lockdown unlock passphrase to the device + void sendLockdownUnlock(in String passphrase, in int bootTtl, in int hourTtl); - /// Lock the device with TAK lock immediately - void sendTakLockNow(); + /// Lock the device immediately (lockdown mode) + void sendLockNow(); } diff --git a/core/service/src/main/kotlin/org/meshtastic/core/service/ServiceRepository.kt b/core/service/src/main/kotlin/org/meshtastic/core/service/ServiceRepository.kt index f4c46a8dd..78f0df650 100644 --- a/core/service/src/main/kotlin/org/meshtastic/core/service/ServiceRepository.kt +++ b/core/service/src/main/kotlin/org/meshtastic/core/service/ServiceRepository.kt @@ -32,26 +32,26 @@ import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject import javax.inject.Singleton -sealed class TakLockState { - data object None : TakLockState() - data object Locked : TakLockState() - data object NeedsProvision : TakLockState() - data object Unlocked : TakLockState() +sealed class LockdownState { + data object None : LockdownState() + data object Locked : LockdownState() + data object NeedsProvision : LockdownState() + data object Unlocked : LockdownState() /** Lock Now ACK received — client should disconnect immediately, no dialog. */ - data object LockNowAcknowledged : TakLockState() + data object LockNowAcknowledged : LockdownState() /** Wrong passphrase — retry immediately. */ - data object UnlockFailed : TakLockState() + data object UnlockFailed : LockdownState() /** Too many attempts — must wait [backoffSeconds] before retrying. */ - data class UnlockBackoff(val backoffSeconds: Int) : TakLockState() + data class UnlockBackoff(val backoffSeconds: Int) : LockdownState() } /** - * TAK session token metadata parsed from the TAK_UNLOCKED:boots=N:until=EPOCH: notification. + * Lockdown session token metadata parsed from the TAK_UNLOCKED:boots=N:until=EPOCH: notification. * * @param bootsRemaining Number of reboots before the token expires. * @param expiryEpoch Unix epoch seconds; 0 means no time-based expiry. */ -data class TakTokenInfo( +data class LockdownTokenInfo( val bootsRemaining: Int, val expiryEpoch: Long, ) @@ -184,26 +184,26 @@ class ServiceRepository @Inject constructor() { _serviceAction.send(action) } - // TAK lock state - private val _takLockState: MutableStateFlow = MutableStateFlow(TakLockState.None) - val takLockState: StateFlow - get() = _takLockState + // Lockdown state + private val _lockdownState: MutableStateFlow = MutableStateFlow(LockdownState.None) + val lockdownState: StateFlow + get() = _lockdownState - fun setTakLockState(state: TakLockState) { - _takLockState.value = state + fun setLockdownState(state: LockdownState) { + _lockdownState.value = state } - fun clearTakLockState() { - _takLockState.value = TakLockState.None + fun clearLockdownState() { + _lockdownState.value = LockdownState.None } - // TAK token info (boots remaining + expiry) from the most recent TAK_UNLOCKED notification - private val _takTokenInfo: MutableStateFlow = MutableStateFlow(null) - val takTokenInfo: StateFlow - get() = _takTokenInfo + // Lockdown token info (boots remaining + expiry) from the most recent TAK_UNLOCKED notification + private val _lockdownTokenInfo: MutableStateFlow = MutableStateFlow(null) + val lockdownTokenInfo: StateFlow + get() = _lockdownTokenInfo - fun setTakTokenInfo(info: TakTokenInfo?) { - _takTokenInfo.value = info + fun setLockdownTokenInfo(info: LockdownTokenInfo?) { + _lockdownTokenInfo.value = info } // True once TAK passphrase is accepted for this BLE connection; false on disconnect. diff --git a/core/service/src/main/kotlin/org/meshtastic/core/service/testing/FakeIMeshService.kt b/core/service/src/main/kotlin/org/meshtastic/core/service/testing/FakeIMeshService.kt index 2cb32bca8..d3e34e7e6 100644 --- a/core/service/src/main/kotlin/org/meshtastic/core/service/testing/FakeIMeshService.kt +++ b/core/service/src/main/kotlin/org/meshtastic/core/service/testing/FakeIMeshService.kt @@ -121,7 +121,7 @@ open class FakeIMeshService : IMeshService.Stub() { override fun requestRebootOta(requestId: Int, destNum: Int, mode: Int, hash: ByteArray?) {} - override fun sendTakUnlock(passphrase: String?, bootTtl: Int, hourTtl: Int) {} + override fun sendLockdownUnlock(passphrase: String?, bootTtl: Int, hourTtl: Int) {} - override fun sendTakLockNow() {} + override fun sendLockNow() {} } diff --git a/core/service/src/test/kotlin/org/meshtastic/core/service/FakeIMeshService.kt b/core/service/src/test/kotlin/org/meshtastic/core/service/FakeIMeshService.kt index 0727b978b..5c325dfc4 100644 --- a/core/service/src/test/kotlin/org/meshtastic/core/service/FakeIMeshService.kt +++ b/core/service/src/test/kotlin/org/meshtastic/core/service/FakeIMeshService.kt @@ -118,7 +118,7 @@ open class FakeIMeshService : IMeshService.Stub() { override fun requestRebootOta(requestId: Int, destNum: Int, mode: Int, hash: ByteArray?) {} - override fun sendTakUnlock(passphrase: String?, bootTtl: Int, hourTtl: Int) {} + override fun sendLockdownUnlock(passphrase: String?, bootTtl: Int, hourTtl: Int) {} - override fun sendTakLockNow() {} + override fun sendLockNow() {} } diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt index f8d74d4b0..e28a28be7 100644 --- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt +++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt @@ -147,11 +147,11 @@ constructor( private val _radioConfigState = MutableStateFlow(RadioConfigState()) val radioConfigState: StateFlow = _radioConfigState - fun sendTakLockNow() { - meshService?.sendTakLockNow() + fun sendLockNow() { + meshService?.sendLockNow() } - val takTokenInfo = serviceRepository.takTokenInfo + val lockdownTokenInfo = serviceRepository.lockdownTokenInfo fun setPreserveFavorites(preserveFavorites: Boolean) { viewModelScope.launch { _radioConfigState.update { it.copy(nodeDbResetPreserveFavorites = preserveFavorites) } } diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/SecurityConfigItemList.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/SecurityConfigItemList.kt index f4c040965..b3bdae937 100644 --- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/SecurityConfigItemList.kt +++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/component/SecurityConfigItemList.kt @@ -87,7 +87,7 @@ import java.security.SecureRandom @Composable fun SecurityConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) { val state by viewModel.radioConfigState.collectAsStateWithLifecycle() - val takTokenInfo by viewModel.takTokenInfo.collectAsStateWithLifecycle(initialValue = null) + val lockdownTokenInfo by viewModel.lockdownTokenInfo.collectAsStateWithLifecycle(initialValue = null) val node by viewModel.destNode.collectAsStateWithLifecycle() val securityConfig = state.radioConfig.security ?: Config.SecurityConfig() val formState = rememberConfigState(initialValue = securityConfig) @@ -266,9 +266,9 @@ fun SecurityConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBa title = "Lock Now", enabled = state.connected, icon = Icons.TwoTone.Warning, - onClick = { viewModel.sendTakLockNow() }, + onClick = { viewModel.sendLockNow() }, ) - takTokenInfo?.let { token -> + lockdownTokenInfo?.let { token -> HorizontalDivider() val expiryMs = token.expiryEpoch * 1000L val expiryText = when {