Skip to content

Commit

Permalink
feat(ndk): add support for reading native C primitives (long, int, si…
Browse files Browse the repository at this point in the history
…ze_t, time_t, bool) in Kotlin
  • Loading branch information
lemnik committed Jan 26, 2024
1 parent 6f5ad4b commit 16267d2
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,42 @@ package com.bugsnag.android.ndk

import java.nio.ByteBuffer

/**
* Read a value of the C type `int`, not to be confused with `getInt` which will always read
* * a Java `int`.
*/
internal fun ByteBuffer.getNativeInt(): Int = getInt()
internal fun ByteBuffer.getNativeLong(): Long = getLong()

/**
* Read a value of the C type `long`, not to be confused with `getLong` which will always read
* a Java `long`.
*/
internal fun ByteBuffer.getNativeLong(): Long =
if (NativeArch.is32bit) getInt().toLong() else getLong()

/**
* Read a value of type `time_t`, the resolution of the value returned *is not defined*. Some of
* our properties are in seconds, others are in milliseconds.
*/
internal fun ByteBuffer.getNativeTime(): Long =
if (NativeArch.is32bit) getInt().toLong() else getLong()

/**
* Read a value of type `size_t`
*/
internal fun ByteBuffer.getNativeSize(): Long = getNativeLong()

/**
* Read a value of type `bool`
*/
internal fun ByteBuffer.getNativeBool(): Boolean = get().toInt() and 0xff != 0

// -------------------------------------------------------------------------------------------------
// Read functions for fixed-width primitives
// -------------------------------------------------------------------------------------------------

internal fun ByteBuffer.getULong(): ULong = getLong().toULong()
internal fun ByteBuffer.getUInt(): UInt = getInt().toUInt()

internal fun ByteBuffer.getCString(byteCount: Int): String {
position(position() + byteCount)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.bugsnag.android.ndk

import android.os.Build
import androidx.annotation.VisibleForTesting

internal object NativeArch {
@JvmField
@VisibleForTesting
var _is32Bit: Boolean? = null

@JvmField
@VisibleForTesting
@Suppress("DEPRECATION")
val supportedAbis: Array<String> = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> Build.SUPPORTED_ABIS
else -> arrayOf(Build.CPU_ABI, Build.CPU_ABI2)
}

val is32bit: Boolean
get() {
var result = _is32Bit
if (result == null) {
result = !supportedAbis.any { it.contains("64") }
_is32Bit = result
}

return result
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.channels.FileChannel

private const val BUGSNAG_EVENT_VERSION = 13

internal object NativeEventDecoder {
fun decode(
event: File,
Expand All @@ -25,7 +27,29 @@ internal object NativeEventDecoder {
eventBytes: ByteBuffer
): Event {
eventBytes.order(ByteOrder.nativeOrder())

val header = decodeHeader(eventBytes)
require(header.version == BUGSNAG_EVENT_VERSION) { "Unsupported event version: ${header.version}" }

if (header.bigEndian == 0) {
eventBytes.order(ByteOrder.BIG_ENDIAN)
}

@Suppress("StopShip") // This is targeting an integration branch
TODO("To be completed")
}

private fun decodeHeader(eventBytes: ByteBuffer): NativeEventHeader {
return NativeEventHeader(
eventBytes.getNativeInt(),
eventBytes.getNativeInt(),
eventBytes.getCString(64)
)
}

private data class NativeEventHeader(
val version: Int,
val bigEndian: Int,
val osBuild: String
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.bugsnag.android.ndk

import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import java.nio.ByteBuffer
import java.nio.ByteOrder

class CPrimitivesTest32Bit {
private val sample32BitData = byteArrayOf(
0x0d, 0, 0, 0, 0, 0, 0, 0, // taken from an event_header (int, int)
0xf4.toByte(), 0x10, 0, 0, 0, 0, 0, 0, // bsg_app_info.duration int64_t
0xa0.toByte(), 0x0f, 0, 0, 0, 0, 0, 0, // bsg_app_info.duration_in_foreground int64_t
0x54, 0x01, 0, 0, 0, 0, 0, 0, // bsg_app_info.duration_ms_offset int64_t
0x54, 0x01, 0, 0, // bsg_app_info.duration_in_foreground_ms_offset time_t
0x01, // bsg_app_info.in_foreground
0x00, // bsg_app_info.is_launching
)

@Before
fun setupArchitecture() {
NativeArch._is32Bit = true
}

@After
fun unsetupArchitecture() {
NativeArch._is32Bit = null
}

@Test
fun test32BitPrimitives() {
val data = ByteBuffer.wrap(sample32BitData)
data.order(ByteOrder.LITTLE_ENDIAN)
assertEquals(13, data.getNativeInt())
assertEquals(0, data.getNativeInt())

assertEquals(4340, data.getLong())
assertEquals(4000, data.getLong())
assertEquals(340, data.getLong())
assertEquals(340, data.getNativeTime())
assertEquals(true, data.getNativeBool())
assertEquals(false, data.getNativeBool())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.bugsnag.android.ndk

import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import java.nio.ByteBuffer
import java.nio.ByteOrder

class CPrimitivesTest64Bit {
private val sample64BitData = byteArrayOf(
0x0d, 0, 0, 0, 0, 0, 0, 0, // taken from an event_header (int, int)
0xf4.toByte(), 0x10, 0, 0, 0, 0, 0, 0, // bsg_app_info.duration int64_t
0xa0.toByte(), 0x0f, 0, 0, 0, 0, 0, 0, // bsg_app_info.duration_in_foreground int64_t
0x54, 0x01, 0, 0, 0, 0, 0, 0, // bsg_app_info.duration_ms_offset int64_t
0x54, 0x01, 0, 0, 0, 0, 0, 0, // bsg_app_info.duration_in_foreground_ms_offset time_t
0x01, // bsg_app_info.in_foreground
0x00, // bsg_app_info.is_launching
)

@Before
fun setupArchitecture() {
NativeArch._is32Bit = false
}

@After
fun unsetupArchitecture() {
NativeArch._is32Bit = null
}

@Test
fun test64BitPrimitives() {
val data = ByteBuffer.wrap(sample64BitData)
data.order(ByteOrder.LITTLE_ENDIAN)
assertEquals(13, data.getNativeInt())
assertEquals(0, data.getNativeInt())

assertEquals(4340, data.getLong())
assertEquals(4000, data.getLong())
assertEquals(340, data.getLong())
assertEquals(340, data.getNativeTime())
assertEquals(true, data.getNativeBool())
assertEquals(false, data.getNativeBool())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,19 @@ import java.nio.ByteBuffer
class NativeEventDecoderTest {
@Test
fun testAarch64Emulator() {
val event = NativeEventDecoder.decodeEventFromBytes(readEventBytes("/aarch64-emulator.crash_dump"))
val event = NativeEventDecoder.decodeEventFromBytes(
readEventBytes("/aarch64-emulator.crash_dump")
)

assertNotNull(event)
}

@Test
fun testArm32Device() {
val event = NativeEventDecoder.decodeEventFromBytes(
readEventBytes("/arm32-droid-razr.crash_dump")
)

assertNotNull(event)
}

Expand Down
Binary file not shown.

0 comments on commit 16267d2

Please sign in to comment.