Cbor: check if inline value classes is marked as @ByteString (#2466)

Fixes #2187 
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoding.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoding.kt
index 0b7a0e0..b77a18c 100644
--- a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoding.kt
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoding.kt
@@ -70,6 +70,8 @@
         if (encodeByteArrayAsByteString && serializer.descriptor == ByteArraySerializer().descriptor) {
             encoder.encodeByteString(value as ByteArray)
         } else {
+            encodeByteArrayAsByteString = encodeByteArrayAsByteString || serializer.descriptor.isInlineByteString()
+
             super.encodeSerializableValue(serializer, value)
         }
     }
@@ -278,6 +280,7 @@
             @Suppress("UNCHECKED_CAST")
             decoder.nextByteString() as T
         } else {
+            decodeByteArrayAsByteString = decodeByteArrayAsByteString || deserializer.descriptor.isInlineByteString()
             super.decodeSerializableValue(deserializer)
         }
     }
@@ -636,6 +639,11 @@
     return getElementAnnotations(index).find { it is ByteString } != null
 }
 
+private fun SerialDescriptor.isInlineByteString(): Boolean {
+    // inline item classes should only have 1 item
+    return isInline && isByteString(0)
+}
+
 
 private val normalizeBaseBits = SINGLE_PRECISION_NORMALIZE_BASE.toBits()
 
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborReaderTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborReaderTest.kt
index edbe5e6..f615d5e 100644
--- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborReaderTest.kt
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborReaderTest.kt
@@ -634,6 +634,34 @@
     }
 
     @Test
+    fun testReadValueClassWithByteString() {
+        assertContentEquals(
+            expected = byteArrayOf(0x11, 0x22, 0x33),
+            actual = Cbor.decodeFromHexString<ValueClassWithByteString>("43112233").x
+        )
+    }
+
+    @Test
+    fun testReadValueClassCustomByteString() {
+        assertEquals(
+            expected = ValueClassWithCustomByteString(CustomByteString(0x11, 0x22, 0x33)),
+            actual = Cbor.decodeFromHexString("43112233")
+        )
+    }
+
+    @Test
+    fun testReadValueClassWithUnlabeledByteString() {
+        assertContentEquals(
+            expected = byteArrayOf(
+                0x11,
+                0x22,
+                0x33
+            ),
+            actual = Cbor.decodeFromHexString<ValueClassWithUnlabeledByteString>("43112233").x.x
+        )
+    }
+
+    @Test
     fun testIgnoresTagsOnStrings() {
         /*
          * 84                                # array(4)
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt
index c546bdf..da7b128 100644
--- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt
@@ -122,4 +122,28 @@
                 actual = Cbor.encodeToHexString(TypeWithNullableCustomByteString(null))
         )
     }
+
+    @Test
+    fun testWriteValueClassWithByteString() {
+        assertEquals(
+            expected = "43112233",
+            actual = Cbor.encodeToHexString(ValueClassWithByteString(byteArrayOf(0x11, 0x22, 0x33)))
+        )
+    }
+
+    @Test
+    fun testWriteValueClassCustomByteString() {
+        assertEquals(
+            expected = "43112233",
+            actual = Cbor.encodeToHexString(ValueClassWithCustomByteString(CustomByteString(0x11, 0x22, 0x33)))
+        )
+    }
+
+    @Test
+    fun testWriteValueClassWithUnlabeledByteString() {
+        assertEquals(
+            expected = "43112233",
+            actual = Cbor.encodeToHexString(ValueClassWithUnlabeledByteString(ValueClassWithUnlabeledByteString.Inner(byteArrayOf(0x11, 0x22, 0x33))))
+        )
+    }
 }
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt
index ad55d04..e4418f4 100644
--- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt
@@ -8,6 +8,7 @@
 import kotlinx.serialization.builtins.*
 import kotlinx.serialization.descriptors.*
 import kotlinx.serialization.encoding.*
+import kotlin.jvm.*
 
 @Serializable
 data class Simple(val a: String)
@@ -110,4 +111,20 @@
 data class TypeWithCustomByteString(@ByteString val x: CustomByteString)
 
 @Serializable
-data class TypeWithNullableCustomByteString(@ByteString val x: CustomByteString?)
\ No newline at end of file
+data class TypeWithNullableCustomByteString(@ByteString val x: CustomByteString?)
+
+@JvmInline
+@Serializable
+value class ValueClassWithByteString(@ByteString val x: ByteArray)
+
+@JvmInline
+@Serializable
+value class ValueClassWithCustomByteString(@ByteString val x: CustomByteString)
+
+@JvmInline
+@Serializable
+value class ValueClassWithUnlabeledByteString(@ByteString val x: Inner) {
+    @JvmInline
+    @Serializable
+    value class Inner(val x: ByteArray)
+}
\ No newline at end of file