From 7204a36953f8b156fa19786e2c26464bc599307b Mon Sep 17 00:00:00 2001 From: ardune <403261+ardune@users.noreply.github.com> Date: Tue, 10 Dec 2024 10:39:43 -0600 Subject: [PATCH] [Cbor] correctly skip structures with ignoreUnknownKeys setting (#2873) --- .../serialization/cbor/internal/Decoder.kt | 6 +- .../cbor/CborSkipTagAndEmptyTest.kt | 60 +++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborSkipTagAndEmptyTest.kt diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Decoder.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Decoder.kt index 174f8fc2ff..88075db26f 100644 --- a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Decoder.kt +++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Decoder.kt @@ -426,9 +426,11 @@ internal class CborParser(private val input: ByteArrayInput, private val verifyO } else { val header = curByte and 0b111_00000 val length = elementLength() - if (header == HEADER_ARRAY || header == HEADER_MAP) { + if (header == HEADER_TAG) { + readNumber() + } else if (header == HEADER_ARRAY || header == HEADER_MAP) { if (length > 0) lengthStack.add(length) - processTags(tags) + else prune(lengthStack) // empty map or array automatically completes } else { input.skip(length) prune(lengthStack) diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborSkipTagAndEmptyTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborSkipTagAndEmptyTest.kt new file mode 100644 index 0000000000..842204e0ad --- /dev/null +++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborSkipTagAndEmptyTest.kt @@ -0,0 +1,60 @@ +package kotlinx.serialization.cbor + +import kotlinx.serialization.* +import kotlin.test.* + +class CborSkipTagAndEmptyTest { + + /** + * A3 # map(3) + * 67 # text(7) + * 76657273696F6E # ""version"" + * 63 # text(3) + * 312E30 # ""1.0"" + * 69 # text(9) + * 646F63756D656E7473 # ""documents"" + * 81 # array(1) + * A1 # map(1) + * 6C # text(12) + * 6465766963655369676E6564 # ""deviceSigned"" + * A2 # map(2) + * 6A # text(10) + * 6E616D65537061636573 # ""nameSpaces"" + * D8 18 # tag(24) <------------------- Testing this skips properly + * 41 # bytes(1) + * A0 # ""\xA0"" + * 6A # text(10) + * 64657669636541757468 # ""deviceAuth"" + * A1 # map(1) + * 69 # text(9) + * 6465766963654D6163 # ""deviceMac"" + * 84 # array(4) + * 43 # bytes(3) + * A10105 + * A0 # map(0) <------------------- Testing this skips properly + * F6 # primitive(22) + * 58 20 # bytes(32) + * E99521A85AD7891B806A07F8B5388A332D92C189A7BF293EE1F543405AE6824D + * 66 # text(6) + * 737461747573 # ""status"" + * 00 # unsigned(0) + */ + private val referenceHexString = "A36776657273696F6E63312E3069646F63756D656E747381A16C6465766963655369676E6564A26A6E616D65537061636573D81841A06A64657669636541757468A1696465766963654D61638443A10105A0F65820E99521A85AD7891B806A07F8B5388A332D92C189A7BF293EE1F543405AE6824D6673746174757300" + + @Test + fun deserializesCorrectly() { + // Specifically, skipping keys with descendants that contain tags and empty maps + val cbor = Cbor{ + ignoreUnknownKeys = true + } + // Prior exception: + // Field 'status' is required for type with serial name 'kotlinx.serialization.cbor.CborSkipTagAndEmptyTest.DataClass', but it was missing + val target = cbor.decodeFromHexString(DataClass.serializer(), referenceHexString) + assertEquals(0, target.status) + } + + @Serializable + data class DataClass( + val status: Int, + ) +} \ No newline at end of file