Skip to content

CBOR: decodeFromByteArray<List<ByteArray>> fails with "Expected start of array, but found XX" for indefinite-length array of byte strings on Android #3012

Closed
@darrenleeleelee1

Description

@darrenleeleelee1

Describe the bug
When attempting to decode a CBOR indefinite-length array (9F ... FF) containing byte strings into a List<ByteArray>, the kotlinx-serialization-cbor library throws a CborDecodingException with the message "Expected start of array, but found XX" (where XX is the major type and length of the first byte string in the array). This occurs even though the input ByteArray correctly starts with the 9F (indefinite-array) marker. The issue seems to be specific to the Android environment or a particular setup therein, as similar code might work in a pure JVM environment.

To Reproduce
The following minimal Kotlin code, when run in an Android Activity's onCreate method, reproduces the issue with a simple indefinite-length array containing one empty byte string (9F40FF):

import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.decodeFromByteArray

// Helper to convert hex string to ByteArray
fun String.hexToByteArray(): ByteArray {
    check(length % 2 == 0) { "Must have an even length" }
    return chunked(2)
        .map { it.toInt(16).toByte() }
        .toByteArray()
}

// Helper to convert ByteArray to hex string for logging
fun ByteArray.toHexString() = joinToString("") { "%02X".format(it) }

@OptIn(ExperimentalSerializationApi::class)
class MainActivity : ComponentActivity() {
    private val TAG = "CBOR_BUG_REPORT"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Test case: Simplified data (an indefinite array with one empty byte string)
        // CBOR: 9F (array*) 40 (bytes(0)) FF (break)
        val hexString = "9F40FF"
        val bytes = hexString.hexToByteArray()

        Log.d(TAG, "Bytes to decode: ${bytes.toHexString()}")
        Log.d(TAG, "First byte as hex: ${bytes[0].toUByte().toString(16).uppercase()}")
        Log.d(TAG, "Is first byte 0x9F? ${bytes[0] == 0x9F.toByte()}")


        try {
            // Attempt to decode as List<ByteArray>
            val result = Cbor.Default.decodeFromByteArray<List<ByteArray>>(bytes)
            Log.d(TAG, "Successfully decoded. Result size: ${result.size}")
            if (result.isNotEmpty()) {
                 Log.d(TAG, "First element (hex): ${result[0].toHexString()}")
            }
        } catch (e: Exception) {
            Log.e(TAG, "Error decoding '${hexString}': ${e.message}", e)
            // Example Stacktrace for "9F40FF":
            // kotlinx.serialization.cbor.internal.CborDecodingException: Expected start of array, but found 40
            //  at kotlinx.serialization.cbor.internal.CborDecodingExceptionKt.CborDecodingException(CborDecodingException.kt:13)
            //  at kotlinx.serialization.cbor.internal.CborParser.startSized-kvxxsfM(Decoder.kt:212)
            //  at kotlinx.serialization.cbor.internal.CborParser.startArray-uLth9ew(Decoder.kt:196)
            //  at kotlinx.serialization.cbor.internal.CborListReader.skipBeginToken-uLth9ew(Decoder.kt:543) // Line number might vary
            //  ...
            //  at kotlinx.serialization.cbor.Cbor.decodeFromByteArray(Cbor.kt:89)
            //  at com.your.package.name.MainActivity.onCreate(MainActivity.kt:LINE_NUMBER_OF_DECODE_CALL)
            //  ...
        }
    }
}

Logcat Output for 9F40FF:

D/CBOR_BUG_REPORT: Bytes to decode: 9F40FF
D/CBOR_BUG_REPORT: First byte as hex: 9F
D/CBOR_BUG_REPORT: Is first byte 0x9F? true
E/CBOR_BUG_REPORT: Error decoding '9F40FF': Expected start of array, but found 40
    kotlinx.serialization.cbor.internal.CborDecodingException: Expected start of array, but found 40
        at kotlinx.serialization.cbor.internal.CborDecodingExceptionKt.CborDecodingException(CborDecodingException.kt:13)
        at kotlinx.serialization.cbor.internal.CborParser.startSized-kvxxsfM(Decoder.kt:212)
        at kotlinx.serialization.cbor.internal.CborParser.startArray-uLth9ew(Decoder.kt:196)
        at kotlinx.serialization.cbor.internal.CborListReader.skipBeginToken-uLth9ew(Decoder.kt:543) 
        at kotlinx.serialization.cbor.internal.CborReader.beginStructure(Decoder.kt:49)
        at kotlinx.serialization.internal.AbstractCollectionSerializer.merge(CollectionSerializers.kt:29)
        at kotlinx.serialization.internal.PrimitiveArraySerializer.deserialize(CollectionSerializers.kt:179)
        // ... (rest of your stack trace) ...
        at com.your.package.name.MainActivity.onCreate(MainActivity.kt:LINE_NUMBER_OF_DECODE_CALL)

Expected behavior

Environment

  • Kotlin version: 2.0.0
  • Library version: 1.8.1
  • Kotlin platforms: Android
  • Gradle version: 8.10.2
  • IDE version: Android Studio Ladybug Feature Drop | 2024.2.2

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions