fix: address top Crashlytics crashes in beta 2.7.14 (#5672)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
James Rich
2026-05-30 08:01:29 -07:00
committed by GitHub
parent ad6144189f
commit c7fbc6e9d3
6 changed files with 66 additions and 19 deletions
@@ -23,13 +23,17 @@ import kotlin.math.roundToLong
object NumberFormatter {
/** Formats a double value with the specified number of decimal places. */
fun format(value: Double, decimalPlaces: Int): String {
if (value.isNaN() || value.isInfinite()) return ""
val factor = 10.0.pow(decimalPlaces)
val rounded = (value * factor).roundToLong()
return formatFixedPoint(rounded, decimalPlaces)
}
/** Formats a float value with the specified number of decimal places. */
fun format(value: Float, decimalPlaces: Int): String = format(value.toDouble(), decimalPlaces)
fun format(value: Float, decimalPlaces: Int): String {
if (value.isNaN() || value.isInfinite()) return ""
return format(value.toDouble(), decimalPlaces)
}
private fun formatFixedPoint(scaledValue: Long, decimalPlaces: Int): String {
if (decimalPlaces == 0) return scaledValue.toString()
@@ -35,4 +35,17 @@ class NumberFormatterTest {
assertEquals("1", NumberFormatter.format(1.23, 0))
assertEquals("-1", NumberFormatter.format(-1.23, 0))
}
@Test
fun testFormatNaN() {
assertEquals("", NumberFormatter.format(Double.NaN, 2))
assertEquals("", NumberFormatter.format(Float.NaN, 1))
}
@Test
fun testFormatInfinity() {
assertEquals("", NumberFormatter.format(Double.POSITIVE_INFINITY, 2))
assertEquals("", NumberFormatter.format(Double.NEGATIVE_INFINITY, 2))
assertEquals("", NumberFormatter.format(Float.POSITIVE_INFINITY, 1))
}
}
@@ -51,7 +51,13 @@ class StoreForwardPacketHandlerImpl(
override fun handleStoreAndForward(packet: MeshPacket, dataPacket: DataPacket, myNodeNum: Int) {
val payload = packet.decoded?.payload ?: return
val u = StoreAndForward.ADAPTER.decode(payload)
val u =
try {
StoreAndForward.ADAPTER.decode(payload)
} catch (e: IOException) {
Logger.e(e) { "Failed to parse StoreAndForward packet" }
return
}
handleReceivedStoreAndForward(dataPacket, u, myNodeNum)
}
@@ -338,4 +338,18 @@ class StoreForwardPacketHandlerImplTest {
verifySuspend { packetRepository.updateSFPPStatus(any(), any(), any(), any(), any(), any(), any()) }
}
// ---------- Legacy S&F: malformed proto ----------
@Test
fun `handleStoreAndForward with malformed payload does not crash`() = testScope.runTest {
val malformedPayload = ByteString.of(0xFF.toByte(), 0xFE.toByte(), 0x07, 0x0E)
val packet =
MeshPacket(from = 999, decoded = Data(portnum = PortNum.STORE_FORWARD_APP, payload = malformedPayload))
val dataPacket = makeDataPacket(999)
// Should not throw — the handler catches the IOException from proto decoding
handler.handleStoreAndForward(packet, dataPacket, myNodeNum)
advanceUntilIdle()
}
}
@@ -86,14 +86,23 @@ internal class SerialConnectionImpl(
}
port.open(usbDeviceConnection)
port.setParameters(115200, UsbSerialPort.DATABITS_8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)
try {
port.setParameters(115200, UsbSerialPort.DATABITS_8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)
// Assert DTR/RTS so native USB-CDC firmware (RAK4631 / nRF52840) recognizes the host as
// present and starts its serial-side Meshtastic protocol. Empirically, omitting these
// signals causes the firmware to never respond to WAKE_BYTES, stalling the handshake at
// Stage 1. Bridge-chip boards (CH340, CP210x, FTDI) tolerate the assertion.
port.dtr = true
port.rts = true
// Assert DTR/RTS so native USB-CDC firmware (RAK4631 / nRF52840) recognizes the host as
// present and starts its serial-side Meshtastic protocol. Empirically, omitting these
// signals causes the firmware to never respond to WAKE_BYTES, stalling the handshake at
// Stage 1. Bridge-chip boards (CH340, CP210x, FTDI) tolerate the assertion.
port.dtr = true
port.rts = true
} catch (e: java.io.IOException) {
Logger.w(e) { "USB control transfer failed during port setup — device may have disconnected" }
closed.set(true)
ignoreException(silent = true) { port.close() }
closedLatch.countDown()
listener.onDisconnected(e)
return
}
Logger.d { "Starting serial reader thread" }
val io =