chore: review-cleanup fleet (audit + fix + hardening) (#5158)

This commit is contained in:
James Rich 2026-04-16 19:02:59 -05:00 committed by GitHub
parent 872c566ef1
commit 17e69c6d4c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
68 changed files with 784 additions and 459 deletions

View file

@ -20,7 +20,6 @@ import co.touchlab.kermit.Logger
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
@ -48,7 +47,7 @@ class BleOtaTransport(
private val scanner: BleScanner,
connectionFactory: BleConnectionFactory,
private val address: String,
dispatcher: CoroutineDispatcher = Dispatchers.Default,
dispatcher: CoroutineDispatcher,
) : UnifiedOtaProtocol {
private val transportScope = CoroutineScope(SupervisorJob() + dispatcher)

View file

@ -27,6 +27,7 @@ import org.meshtastic.core.common.util.CommonUri
import org.meshtastic.core.common.util.NumberFormatter
import org.meshtastic.core.common.util.ioDispatcher
import org.meshtastic.core.database.entity.FirmwareRelease
import org.meshtastic.core.di.CoroutineDispatchers
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.model.RadioController
import org.meshtastic.core.repository.NodeRepository
@ -67,7 +68,7 @@ private const val GATT_RELEASE_DELAY_MS = 1000L
*
* All platform I/O (file reading, content-resolver imports) is delegated to [FirmwareFileHandler].
*/
@Suppress("TooManyFunctions")
@Suppress("TooManyFunctions", "LongParameterList")
@Single
class Esp32OtaUpdateHandler(
private val firmwareRetriever: FirmwareRetriever,
@ -76,6 +77,7 @@ class Esp32OtaUpdateHandler(
private val nodeRepository: NodeRepository,
private val bleScanner: BleScanner,
private val bleConnectionFactory: BleConnectionFactory,
private val dispatchers: CoroutineDispatchers,
) : FirmwareUpdateHandler {
/** Entry point for FirmwareUpdateHandler interface. Routes to BLE (MAC with colons) or WiFi (IP without). */
@ -102,7 +104,7 @@ class Esp32OtaUpdateHandler(
hardware = hardware,
updateState = updateState,
firmwareUri = firmwareUri,
transportFactory = { BleOtaTransport(bleScanner, bleConnectionFactory, address) },
transportFactory = { BleOtaTransport(bleScanner, bleConnectionFactory, address, dispatchers.default) },
rebootMode = 1,
connectionAttempts = 5,
)

View file

@ -167,7 +167,7 @@ class WifiOtaTransport(private val deviceIpAddress: String, private val port: In
override suspend fun close() {
withContext(ioDispatcher) {
runCatching {
safeCatching {
socket?.close()
selectorManager?.close()
}

View file

@ -28,6 +28,7 @@ import org.meshtastic.core.common.util.CommonUri
import org.meshtastic.core.common.util.NumberFormatter
import org.meshtastic.core.common.util.ioDispatcher
import org.meshtastic.core.database.entity.FirmwareRelease
import org.meshtastic.core.di.CoroutineDispatchers
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.model.RadioController
import org.meshtastic.core.resources.Res
@ -70,6 +71,7 @@ class SecureDfuHandler(
private val radioController: RadioController,
private val bleScanner: BleScanner,
private val bleConnectionFactory: BleConnectionFactory,
private val dispatchers: CoroutineDispatchers,
) : FirmwareUpdateHandler {
@Suppress("LongMethod")
@ -108,7 +110,7 @@ class SecureDfuHandler(
var transport: SecureDfuTransport? = null
var completed = false
try {
transport = SecureDfuTransport(bleScanner, bleConnectionFactory, target)
transport = SecureDfuTransport(bleScanner, bleConnectionFactory, target, dispatchers.default)
transport.triggerButtonlessDfu().onFailure { e ->
Logger.w(e) { "DFU: Buttonless trigger failed ($e) — device may already be in DFU mode" }

View file

@ -30,7 +30,6 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.cancel
@ -67,7 +66,7 @@ class SecureDfuTransport(
private val scanner: BleScanner,
connectionFactory: BleConnectionFactory,
private val address: String,
dispatcher: CoroutineDispatcher = Dispatchers.Default,
dispatcher: CoroutineDispatcher,
) {
private val transportScope = CoroutineScope(SupervisorJob() + dispatcher)
private val bleConnection = connectionFactory.create(transportScope, "Secure DFU")
@ -252,7 +251,7 @@ class SecureDfuTransport(
* accept a fresh DFU session.
*/
suspend fun abort() {
runCatching {
safeCatching {
bleConnection.profile(SecureDfuUuids.SERVICE) { service ->
val controlChar = service.characteristic(SecureDfuUuids.CONTROL_POINT)
service.write(controlChar, byteArrayOf(DfuOpcode.ABORT), BleWriteType.WITH_RESPONSE)
@ -264,7 +263,7 @@ class SecureDfuTransport(
/** Disconnect from the DFU target and cancel the transport coroutine scope. */
suspend fun close() {
runCatching { bleConnection.disconnect() }.onFailure { Logger.w(it) { "DFU: Error during disconnect" } }
safeCatching { bleConnection.disconnect() }.onFailure { Logger.w(it) { "DFU: Error during disconnect" } }
transportScope.cancel()
}

View file

@ -20,9 +20,11 @@ import dev.mokkery.MockMode
import dev.mokkery.answering.returns
import dev.mokkery.every
import dev.mokkery.mock
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import org.meshtastic.core.ble.BleConnectionFactory
import org.meshtastic.core.ble.BleScanner
import org.meshtastic.core.di.CoroutineDispatchers
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.model.RadioController
import org.meshtastic.core.repository.NodeRepository
@ -59,6 +61,12 @@ class DefaultFirmwareUpdateManagerTest {
private val bleScanner: BleScanner = mock(MockMode.autofill)
private val bleConnectionFactory: BleConnectionFactory = mock(MockMode.autofill)
private val firmwareRetriever = FirmwareRetriever(fileHandler)
private val dispatchers =
CoroutineDispatchers(
io = Dispatchers.Unconfined,
main = Dispatchers.Unconfined,
default = Dispatchers.Unconfined,
)
private val secureDfuHandler =
SecureDfuHandler(
@ -67,6 +75,7 @@ class DefaultFirmwareUpdateManagerTest {
radioController = radioController,
bleScanner = bleScanner,
bleConnectionFactory = bleConnectionFactory,
dispatchers = dispatchers,
)
private val usbUpdateHandler =
@ -84,6 +93,7 @@ class DefaultFirmwareUpdateManagerTest {
nodeRepository = nodeRepository,
bleScanner = bleScanner,
bleConnectionFactory = bleConnectionFactory,
dispatchers = dispatchers,
)
private fun createManager(address: String?): DefaultFirmwareUpdateManager {