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

@ -127,7 +127,10 @@ compose.desktop {
isEnabled.set(true)
obfuscate.set(false) // Open-source project — obfuscation adds no value
optimize.set(true)
configurationFiles.from(project.file("proguard-rules.pro"))
configurationFiles.from(
rootProject.file("config/proguard/shared-rules.pro"),
project.file("proguard-rules.pro"),
)
}
nativeDistributions {

View file

@ -4,202 +4,56 @@
# Open-source project: we rely on tree-shaking (unused code removal) for size
# reduction. Obfuscation is disabled in build.gradle.kts (obfuscate.set(false)).
#
# Key libraries requiring keep-rules (reflection, JNI, code generation):
# Koin (DI via reflection), kotlinx-serialization (generated serializers),
# Wire protobuf (ADAPTER reflection), Room KMP (generated DB + converters),
# Ktor (Java engine + ServiceLoader), Kable BLE, Coil, Compose Multiplatform
# resources, SQLite bundled (JNI), AboutLibraries.
# Cross-platform library rules (Koin, kotlinx-serialization, Wire, Room,
# Ktor, Coil, Kable, Kermit, Okio, DataStore, Paging, Lifecycle, Navigation 3,
# AboutLibraries, Markdown, QRCode, CMP resources, core model) live in
# config/proguard/shared-rules.pro and are wired in by this module's
# build.gradle.kts. This file holds only desktop/JVM-specific rules.
# ============================================================================
# ---- General ----------------------------------------------------------------
# Preserve line numbers for meaningful stack traces
-keepattributes SourceFile,LineNumberTable,*Annotation*,Signature,InnerClasses,EnclosingMethod,Exceptions
# Suppress notes about duplicate resource files (common in fat JARs)
-dontnote **
# Disable ProGuard optimization passes. Tree-shaking (unused code removal) still
# runs only method-body rewrites and call-site transformations are suppressed.
#
# Why: CMP 1.11 ships consumer rules with -assumenosideeffects on
# Composer.<clinit>() and ComposerImpl.<clinit>(), plus -assumevalues on
# ComposeRuntimeFlags and ComposeStackTraceMode. These optimization directives
# let the optimizer rewrite *call sites* (class-init triggers, flag reads) even
# when the target classes are preserved by -keep rules. The result is that the
# Compose recomposer/frame-clock/animation state machines silently freeze on
# their first frame in release builds. -dontoptimize is the only directive that
# disables processing of -assumenosideeffects/-assumevalues. The desktop compose
# build sets optimize.set(true), so this applies here as well as to R8. See #5146.
-dontoptimize
# Do not parse/rewrite Kotlin metadata during shrinking/optimization.
# ProGuard's KotlinShrinker cannot handle the metadata produced by Compose
# Multiplatform 1.11.x + Kotlin 2.3.x, causing a NullPointerException.
# Since we disable obfuscation (class names remain stable), metadata references
# stay valid and do not need rewriting. The annotations themselves are preserved
# by -keepattributes *Annotation*.
#
# NOTE: -dontprocesskotlinmetadata is a ProGuard-only directive; R8 does not
# recognize it, which is why it lives in the desktop-only file.
-dontprocesskotlinmetadata
# ---- Entry point ------------------------------------------------------------
-keep class org.meshtastic.desktop.MainKt { *; }
# ---- Kotlin / Coroutines ---------------------------------------------------
# ---- Ktor Java engine (desktop-only; Android uses OkHttp) -------------------
# Keep Kotlin metadata for reflection-dependent libraries
-keep class kotlin.Metadata { *; }
-keep class kotlin.reflect.** { *; }
# Coroutines internals
-dontwarn kotlinx.coroutines.**
-keep class kotlinx.coroutines.** { *; }
-keep class kotlin.coroutines.Continuation { *; }
# ---- Koin DI (reflection-based injection) -----------------------------------
# Koin core uses reflection to instantiate definitions
-keep class org.koin.** { *; }
-dontwarn org.koin.**
# Keep all Koin-annotated @Module / @ComponentScan classes and their generated
# counterparts so Koin K2 plugin output survives tree-shaking.
-keep @org.koin.core.annotation.Module class * { *; }
-keep @org.koin.core.annotation.ComponentScan class * { *; }
-keep @org.koin.core.annotation.Single class * { *; }
-keep @org.koin.core.annotation.Factory class * { *; }
# Generated Koin module extensions (K2 plugin output)
-keep class org.meshtastic.**.di.** { *; }
# ---- kotlinx-serialization --------------------------------------------------
# The serialization plugin generates companion $serializer classes and
# serializer() factory methods that are invoked reflectively.
-keepattributes RuntimeVisibleAnnotations
-keep class kotlinx.serialization.** { *; }
-dontwarn kotlinx.serialization.**
# Keep @Serializable classes and their generated serializers
-keepclassmembers @kotlinx.serialization.Serializable class ** {
# Companion object that holds the serializer() factory
static ** Companion;
kotlinx.serialization.KSerializer serializer(...);
}
-keepclassmembers class **.$serializer { *; }
-keep class **.$serializer { *; }
-keepclasseswithmembers class ** {
kotlinx.serialization.KSerializer serializer(...);
}
# ---- Wire protobuf ----------------------------------------------------------
# Wire generates ADAPTER companion objects accessed via reflection
-keep class com.squareup.wire.** { *; }
-dontwarn com.squareup.wire.**
# All generated proto message classes
-keep class org.meshtastic.proto.** { *; }
-keep class meshtastic.** { *; }
# Suppress warnings about missing Android Parcelable (Wire cross-platform stubs)
-dontwarn android.os.Parcel**
-dontwarn android.os.Parcelable**
# ---- Room KMP ---------------------------------------------------------------
# Preserve generated database constructors (required for Room's reflective init)
-keep class * extends androidx.room3.RoomDatabase { <init>(); }
-keep class * implements androidx.room3.RoomDatabaseConstructor { *; }
# Keep the expect/actual MeshtasticDatabaseConstructor
-keep class org.meshtastic.core.database.MeshtasticDatabaseConstructor { *; }
-keep class org.meshtastic.core.database.MeshtasticDatabase { *; }
# Room DAOs Room generates implementations at compile time; keep interfaces
-keep class org.meshtastic.core.database.dao.** { *; }
# Room Entities accessed via reflection for column mapping
-keep class org.meshtastic.core.database.entity.** { *; }
# Room TypeConverters invoked reflectively
-keep class org.meshtastic.core.database.Converters { *; }
# Room generated _Impl classes
-keep class **_Impl { *; }
# ---- SQLite bundled (JNI) ---------------------------------------------------
-keep class androidx.sqlite.** { *; }
-dontwarn androidx.sqlite.**
# ---- Ktor (Java engine + ServiceLoader + content negotiation) ---------------
# Ktor uses ServiceLoader and reflection for engine/plugin discovery
-keep class io.ktor.** { *; }
-dontwarn io.ktor.**
# Keep ServiceLoader metadata files
-keepclassmembers class * implements io.ktor.client.HttpClientEngineFactory { *; }
# Java HTTP client engine
-keep class io.ktor.client.engine.java.** { *; }
# ---- Coil (image loading) ---------------------------------------------------
-keep class coil3.** { *; }
-dontwarn coil3.**
# ---- Kable BLE --------------------------------------------------------------
-keep class com.juul.kable.** { *; }
-dontwarn com.juul.kable.**
# ---- Compose Multiplatform resources ----------------------------------------
# Generated resource accessor classes (Res.string.*, Res.drawable.*, etc.)
-keep class org.jetbrains.compose.resources.** { *; }
-keep class org.meshtastic.core.resources.** { *; }
# ---- AboutLibraries ---------------------------------------------------------
-keep class com.mikepenz.aboutlibraries.** { *; }
-dontwarn com.mikepenz.aboutlibraries.**
# ---- Multiplatform Markdown Renderer ----------------------------------------
-keep class com.mikepenz.markdown.** { *; }
-dontwarn com.mikepenz.markdown.**
# ---- QR Code Kotlin ---------------------------------------------------------
-keep class io.github.g0dkar.qrcode.** { *; }
-dontwarn io.github.g0dkar.qrcode.**
-keep class qrcode.** { *; }
-dontwarn qrcode.**
# ---- Kermit logging ----------------------------------------------------------
-keep class co.touchlab.kermit.** { *; }
-dontwarn co.touchlab.kermit.**
# ---- Okio -------------------------------------------------------------------
-dontwarn okio.**
-keep class okio.** { *; }
# ---- DataStore --------------------------------------------------------------
-keep class androidx.datastore.** { *; }
-dontwarn androidx.datastore.**
# ---- Paging -----------------------------------------------------------------
-keep class androidx.paging.** { *; }
-dontwarn androidx.paging.**
# ---- Lifecycle / Navigation / ViewModel (JetBrains forks) -------------------
-keep class androidx.lifecycle.** { *; }
-keep class androidx.navigation3.** { *; }
-dontwarn androidx.lifecycle.**
-dontwarn androidx.navigation3.**
# ---- Meshtastic application code --------------------------------------------
# ---- Meshtastic desktop host shell ------------------------------------------
# Keep all desktop module classes (thin host shell not worth tree-shaking)
-keep class org.meshtastic.desktop.** { *; }
# Core model classes (used in serialization, Room, and Koin injection)
-keep class org.meshtastic.core.model.** { *; }
# ---- JVM runtime suppression ------------------------------------------------
-dontwarn java.lang.reflect.**

View file

@ -60,6 +60,7 @@ import io.ktor.client.HttpClient
import kotlinx.coroutines.flow.first
import okio.Path.Companion.toPath
import org.jetbrains.compose.resources.decodeToSvgPainter
import org.jetbrains.compose.resources.stringResource
import org.koin.compose.koinInject
import org.koin.core.context.startKoin
import org.meshtastic.core.common.BuildConfigProvider
@ -70,6 +71,10 @@ import org.meshtastic.core.navigation.SettingsRoute
import org.meshtastic.core.navigation.TopLevelDestination
import org.meshtastic.core.navigation.rememberMultiBackstack
import org.meshtastic.core.repository.UiPrefs
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.desktop_tray_quit
import org.meshtastic.core.resources.desktop_tray_show
import org.meshtastic.core.resources.desktop_tray_tooltip
import org.meshtastic.core.service.MeshServiceOrchestrator
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.core.ui.viewmodel.UIViewModel
@ -216,11 +221,11 @@ private fun ApplicationScope.MeshtasticDesktopApp(
Tray(
state = trayState,
icon = trayIcon,
tooltip = "Meshtastic Desktop",
tooltip = stringResource(Res.string.desktop_tray_tooltip),
onAction = { isAppVisible = true },
menu = {
Item("Show Meshtastic", onClick = { isAppVisible = true })
Item("Quit", onClick = ::exitApplication)
Item(stringResource(Res.string.desktop_tray_show), onClick = { isAppVisible = true })
Item(stringResource(Res.string.desktop_tray_quit), onClick = ::exitApplication)
},
)

View file

@ -22,6 +22,7 @@ import org.meshtastic.core.repository.MeshServiceNotifications
import org.meshtastic.core.repository.Notification
import org.meshtastic.core.repository.NotificationManager
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.desktop_notification_title
import org.meshtastic.core.resources.getString
import org.meshtastic.core.resources.low_battery_message
import org.meshtastic.core.resources.low_battery_title
@ -141,7 +142,7 @@ class DesktopMeshServiceNotifications(private val notificationManager: Notificat
override fun showClientNotification(clientNotification: ClientNotification) {
notificationManager.dispatch(
Notification(
title = "Meshtastic",
title = getString(Res.string.desktop_notification_title),
message = clientNotification.message,
category = Notification.Category.Alert,
id = clientNotification.toString().hashCode(),

View file

@ -80,6 +80,10 @@ class NoopRadioInterfaceService : RadioInterfaceService {
logWarn("NoopRadioInterfaceService.sendToRadio(${bytes.size} bytes)")
}
override fun resetReceivedBuffer() {
// No-op: this stub never buffers bytes.
}
override fun connect() {
logWarn("NoopRadioInterfaceService.connect()")
}