diff --git a/wire-runtime-swift/src/main/swift/ProtoCodable/ProtoDecoder.swift b/wire-runtime-swift/src/main/swift/ProtoCodable/ProtoDecoder.swift index 748eb274d8..e4c0eedb1f 100644 --- a/wire-runtime-swift/src/main/swift/ProtoCodable/ProtoDecoder.swift +++ b/wire-runtime-swift/src/main/swift/ProtoCodable/ProtoDecoder.swift @@ -304,12 +304,17 @@ public final class ProtoDecoder { count: buffer.count ) - while fullBuffer.isDataRemaining, let size = try? fullBuffer.readVarint() { + while fullBuffer.isDataRemaining { + let size = try fullBuffer.readVarint() if size == 0 { break } + guard let size = Int(exactly: size) else { + throw ProtoDecoder.Error.unexpectedEndOfData + } + try fullBuffer.verifyAdditional(count: size) let messageBuffer = ReadBuffer( storage: fullBuffer.pointer, - count: Int(size) + count: size ) let reader = ProtoReader( @@ -320,7 +325,7 @@ public final class ProtoDecoder { values.append(try reader.decode(type)) // Advance the buffer before reading the next item in the stream. - _ = try fullBuffer.readBuffer(count: Int(size)) + _ = try fullBuffer.readBuffer(count: size) } } diff --git a/wire-runtime-swift/src/main/swift/ProtoCodable/ProtoReader.swift b/wire-runtime-swift/src/main/swift/ProtoCodable/ProtoReader.swift index 0951b98aff..ae1e9eaa8a 100644 --- a/wire-runtime-swift/src/main/swift/ProtoCodable/ProtoReader.swift +++ b/wire-runtime-swift/src/main/swift/ProtoCodable/ProtoReader.swift @@ -141,7 +141,7 @@ public final class ProtoReader { let frame = MessageFrame( isProto3: isProto3Message, - messageEnd: buffer.pointer.advanced(by: length) + messageEnd: try buffer.endPointer(count: length) ) messageStack.advanced(by: messageStackIndex).initialize(to: frame) @@ -875,6 +875,8 @@ public final class ProtoReader { return } + let messageEnd = try buffer.endPointer(count: length) + // Preallocate space for the unpacked data. // It's allowable to have a packed field spread across multiple places // in the buffer, so add to the existing capacity. @@ -885,13 +887,16 @@ public final class ProtoReader { array.reserveCapacity(array.count + (length / MemoryLayout.size)) // This is a packed field, so keep decoding until we're out of bytes. - let messageEnd = buffer.pointer.advanced(by: length) while buffer.pointer < messageEnd { // Reading a scalar will set the state to `.tag` because we assume // we're reading a single value most of the time and are then done. // Since we're in a repeated field we'll keep reading values though. state = .packedValue - if let decodedVal = try decode() { + let decodedVal = try decode() + guard buffer.pointer <= messageEnd else { + throw ProtoDecoder.Error.unexpectedEndOfData + } + if let decodedVal = decodedVal { array.append(decodedVal) } } diff --git a/wire-runtime-swift/src/main/swift/ProtoCodable/ReadBuffer.swift b/wire-runtime-swift/src/main/swift/ProtoCodable/ReadBuffer.swift index 605578e6e7..266a23d931 100644 --- a/wire-runtime-swift/src/main/swift/ProtoCodable/ReadBuffer.swift +++ b/wire-runtime-swift/src/main/swift/ProtoCodable/ReadBuffer.swift @@ -61,15 +61,22 @@ final class ReadBuffer { } init(storage: UnsafePointer, count: Int) { + precondition(count >= 0) self.start = storage self.end = storage.advanced(by: count) self.pointer = storage } func verifyAdditional(count: Int) throws { - guard count >= 0, pointer.advanced(by: count) <= end else { + _ = try endPointer(count: count) + } + + func endPointer(count: Int) throws -> UnsafePointer { + let remainingCount = end - pointer + guard count >= 0, remainingCount >= 0, count <= remainingCount else { throw ProtoDecoder.Error.unexpectedEndOfData } + return pointer.advanced(by: count) } } @@ -79,39 +86,39 @@ extension ReadBuffer { // MARK: - Reading func readBuffer(count: Int) throws -> UnsafeRawBufferPointer { - try verifyAdditional(count: count) + let newPointer = try endPointer(count: count) let buffer = UnsafeRawBufferPointer(start: pointer, count: count) - pointer = pointer.advanced(by: count) + pointer = newPointer return buffer } func readData(count: Int) throws -> Data { - try verifyAdditional(count: count) + let newPointer = try endPointer(count: count) let data = Data(bytes: pointer, count: count) - pointer = pointer.advanced(by: count) + pointer = newPointer return data } func readFixed32() throws -> UInt32 { - try verifyAdditional(count: 4) + let newPointer = try endPointer(count: 4) var value: UInt32 = 0 withUnsafeMutableBytes(of: &value) { dest -> Void in dest.copyMemory(from: UnsafeRawBufferPointer(start: pointer, count: 4)) } - pointer = pointer.advanced(by: 4) + pointer = newPointer return UInt32(littleEndian: value) } func readFixed64() throws -> UInt64 { - try verifyAdditional(count: 8) + let newPointer = try endPointer(count: 8) var value: UInt64 = 0 withUnsafeMutableBytes(of: &value) { dest -> Void in dest.copyMemory(from: UnsafeRawBufferPointer(start: pointer, count: 8)) } - pointer = pointer.advanced(by: 8) + pointer = newPointer return UInt64(littleEndian: value) } @@ -122,6 +129,7 @@ extension ReadBuffer { var result: UInt64 = 0 while shift < 64 { + try verifyAdditional(count: 1) let byte = pointer.pointee pointer = pointer.advanced(by: 1) @@ -130,8 +138,6 @@ extension ReadBuffer { return result } shift += 7 - - try verifyAdditional(count: 1) } throw ProtoDecoder.Error.malformedVarint } diff --git a/wire-runtime-swift/src/test/swift/ProtoDecoderTests.swift b/wire-runtime-swift/src/test/swift/ProtoDecoderTests.swift index 46fae44920..1e4018cf20 100644 --- a/wire-runtime-swift/src/test/swift/ProtoDecoderTests.swift +++ b/wire-runtime-swift/src/test/swift/ProtoDecoderTests.swift @@ -32,6 +32,38 @@ final class ProtoDecoderTests: XCTestCase { XCTAssertEqual(object, []) } + func testDecodeSizeDelimitedRejectsUnrepresentableSize() throws { + let decoder = ProtoDecoder() + let data = Foundation.Data(hexEncoded: """ + FFFFFFFFFFFFFFFFFF01 // UInt64.max + """)! + + XCTAssertThrowsError( + try decoder.decodeSizeDelimited(SimpleOptional2.self, from: data) + ) { error in + guard case ProtoDecoder.Error.unexpectedEndOfData = error else { + XCTFail("Unexpected error: \(error)") + return + } + } + } + + func testDecodeSizeDelimitedRejectsTruncatedSize() throws { + let decoder = ProtoDecoder() + let data = Foundation.Data(hexEncoded: """ + 80 // Truncated size varint + """)! + + XCTAssertThrowsError( + try decoder.decodeSizeDelimited(SimpleOptional2.self, from: data) + ) { error in + guard case ProtoDecoder.Error.unexpectedEndOfData = error else { + XCTFail("Unexpected error: \(error)") + return + } + } + } + func testDecodeEmptyDataTwice() throws { let decoder = ProtoDecoder() // The empty message case is optimized to reuse objects, so make sure diff --git a/wire-runtime-swift/src/test/swift/ProtoReaderTests.swift b/wire-runtime-swift/src/test/swift/ProtoReaderTests.swift index 7093759b45..ef48616e10 100644 --- a/wire-runtime-swift/src/test/swift/ProtoReaderTests.swift +++ b/wire-runtime-swift/src/test/swift/ProtoReaderTests.swift @@ -646,6 +646,23 @@ final class ProtoReaderTests: XCTestCase { } } + func testDecodePackedRepeatedFixedUInt32RejectsPartialValue() throws { + let data = Foundation.Data(hexEncoded: """ + 0A // (Tag 1 | Length Delimited) + 02 // Length 2 + 01000000 // Enough bytes in the message, but not in the packed value + """)! + + XCTAssertThrowsError( + try test(data: data) { reader in + var values: [UInt32] = [] + try reader.decode(tag: 1) { try reader.decode(into: &values, encoding: .fixed) } + } + ) { error in + assertUnexpectedEndError(error) + } + } + func testDecodePackedRepeatedFixedUInt32Empty() throws { let data = Foundation.Data(hexEncoded: """ 0A // (Tag 1 | Length Delimited) @@ -1147,6 +1164,64 @@ final class ProtoReaderTests: XCTestCase { } } + func testReadVarintRejectsMissingValue() throws { + let data = Foundation.Data(hexEncoded: """ + 08 // (Tag 1 | Varint) + """)! + + XCTAssertThrowsError( + try test(data: data) { reader in + _ = try reader.forEachTag { tag in + XCTAssertEqual(tag, 1) + _ = try reader.readVarint() + } + } + ) { error in + assertUnexpectedEndError(error) + } + } + + func testNestedMessageRejectsOversizedLength() throws { + let data = Foundation.Data(hexEncoded: """ + 12 // (Tag 2 | Length Delimited) + FFFFFFFF07 // Length Int32.max + """)! + + XCTAssertThrowsError( + try test(data: data) { reader in + _ = try reader.forEachTag { tag in + switch tag { + case 2: _ = try reader.decode(NestedMessage.self) + default: XCTFail("Unexpected field") + } + } + } + ) { error in + assertUnexpectedEndError(error) + } + } + + func testPackedRepeatedRejectsOversizedLengthBeforePreallocation() throws { + let data = Foundation.Data(hexEncoded: """ + 0A // (Tag 1 | Length Delimited) + FFFFFFFF07 // Length Int32.max + """)! + + XCTAssertThrowsError( + try test(data: data) { reader in + var values: [Bool] = [] + _ = try reader.forEachTag { tag in + switch tag { + case 1: try reader.decode(into: &values) + default: XCTFail("Unexpected field") + } + } + } + ) { error in + assertUnexpectedEndError(error) + } + } + // MARK: - Tests - Unknown Fields func testUnknownFields() throws { @@ -1374,6 +1449,13 @@ final class ProtoReaderTests: XCTestCase { } XCTAssertEqual(message, "Negative length: -128. Reader position: \(readerPosition). Last read tag: 1.") } + + private func assertUnexpectedEndError(_ error: Error) { + guard case ProtoDecoder.Error.unexpectedEndOfData = error else { + XCTFail("Unexpected error: \(error)") + return + } + } } // MARK: - diff --git a/wire-runtime/src/commonMain/kotlin/com/squareup/wire/ByteArrayProtoReader32.kt b/wire-runtime/src/commonMain/kotlin/com/squareup/wire/ByteArrayProtoReader32.kt index dfc3b6f028..61bcc0b1a9 100644 --- a/wire-runtime/src/commonMain/kotlin/com/squareup/wire/ByteArrayProtoReader32.kt +++ b/wire-runtime/src/commonMain/kotlin/com/squareup/wire/ByteArrayProtoReader32.kt @@ -62,6 +62,15 @@ internal class ByteArrayProtoReader32( /** The absolute position of the end of the current message. */ private var limit: Int = source.size, ) : ProtoReader32 { + init { + require(pos in 0..source.size) { + "pos=$pos must be between 0 and source size ${source.size}" + } + require(limit in pos..source.size) { + "limit=$limit must be between pos=$pos and source size ${source.size}" + } + } + /** The current number of levels of message nesting. */ private var recursionDepth = 0 @@ -129,12 +138,12 @@ internal class ByteArrayProtoReader32( nextFieldEncoding = FieldEncoding.LENGTH_DELIMITED state = STATE_LENGTH_DELIMITED val length = internalReadVarint32() - if (length < 0) throw ProtocolException("Negative length: $length. Reader position: $pos. Last read tag: $tag.") + requireNonNegativeLength(length) if (pushedLimit != -1) throw IllegalStateException() + val newLimit = checkedLimit(length) // Push the current limit, and set a new limit to the length of this value. pushedLimit = limit - limit = pos + length - if (limit > pushedLimit) throw EOFException() + limit = newLimit return length } @@ -228,7 +237,7 @@ internal class ByteArrayProtoReader32( } STATE_LENGTH_DELIMITED -> { val length = internalReadVarint32() - if (length < 0) throw ProtocolException("Negative length: $length. Reader position: $pos. Last read tag: $tag.") + requireNonNegativeLength(length, tag) skip(length) } STATE_VARINT -> { @@ -386,7 +395,7 @@ internal class ByteArrayProtoReader32( if (state != STATE_LENGTH_DELIMITED) { throw ProtocolException("Expected LENGTH_DELIMITED but was $state. Reader position: $pos. Last read tag: $tag.") } - val byteCount = limit - pos + val byteCount = remainingInLimit() state = STATE_TAG // We've completed a length-delimited scalar. Pop the limit. limit = pushedLimit @@ -414,7 +423,7 @@ internal class ByteArrayProtoReader32( override fun nextFieldMinLengthInBytes(): Int { return when (nextFieldEncoding) { - FieldEncoding.LENGTH_DELIMITED -> limit - pos + FieldEncoding.LENGTH_DELIMITED -> remainingInLimit() FieldEncoding.FIXED32 -> 4 FieldEncoding.FIXED64 -> 8 FieldEncoding.VARINT -> 1 @@ -423,34 +432,30 @@ internal class ByteArrayProtoReader32( } private fun skip(byteCount: Int) { - val newPos = pos + byteCount - if (newPos > limit) throw EOFException() - pos = newPos + pos = checkedLimit(byteCount) } private fun readByteString(byteCount: Int): ByteString { - val newPos = pos + byteCount - if (newPos > limit) throw EOFException() + val newPos = checkedLimit(byteCount) val result = source.toByteString(pos, byteCount) pos = newPos return result } private fun readUtf8(byteCount: Int): String { - val newPos = pos + byteCount - if (newPos > limit) throw EOFException() + val newPos = checkedLimit(byteCount) val result = source.decodeToString(startIndex = pos, endIndex = newPos) pos = newPos return result } private fun readByte(): Byte { - if (pos == limit) throw EOFException() + checkedLimit(1) return source[pos++] } private fun readIntLe(): Int { - if (pos + 4 > limit) throw EOFException() + checkedLimit(4) val result = ( (source[pos++] and 0xff) @@ -463,7 +468,7 @@ internal class ByteArrayProtoReader32( } private fun readLongLe(): Long { - if (pos + 8 > limit) throw EOFException() + checkedLimit(8) val result = ( (source[pos++] and 0xffL) @@ -478,4 +483,20 @@ internal class ByteArrayProtoReader32( return result } + + private fun requireNonNegativeLength(length: Int, lastReadTag: Int = tag) { + if (length < 0) { + throw ProtocolException("Negative length: $length. Reader position: $pos. Last read tag: $lastReadTag.") + } + } + + private fun checkedLimit(byteCount: Int): Int { + if (byteCount < 0 || byteCount > remainingInLimit()) throw EOFException() + return pos + byteCount + } + + private fun remainingInLimit(): Int { + if (pos > limit) throw EOFException() + return limit - pos + } } diff --git a/wire-runtime/src/commonMain/kotlin/com/squareup/wire/ProtoReader.kt b/wire-runtime/src/commonMain/kotlin/com/squareup/wire/ProtoReader.kt index 99adb4524f..7bf978d322 100644 --- a/wire-runtime/src/commonMain/kotlin/com/squareup/wire/ProtoReader.kt +++ b/wire-runtime/src/commonMain/kotlin/com/squareup/wire/ProtoReader.kt @@ -154,12 +154,12 @@ open class ProtoReader(private val source: BufferedSource) { nextFieldEncoding = FieldEncoding.LENGTH_DELIMITED state = STATE_LENGTH_DELIMITED val length = internalReadVarint32() - if (length < 0) throw ProtocolException("Negative length: $length. Reader position: $pos. Last read tag: $tag.") + requireNonNegativeLength(length) if (pushedLimit != -1L) throw IllegalStateException() + val newLimit = checkedLimit(length.toLong()) // Push the current limit, and set a new limit to the length of this value. pushedLimit = limit - limit = pos + length - if (limit > pushedLimit) throw EOFException() + limit = newLimit return length } @@ -267,9 +267,8 @@ open class ProtoReader(private val source: BufferedSource) { } STATE_LENGTH_DELIMITED -> { val length = internalReadVarint32() - if (length < 0) throw ProtocolException("Negative length: $length. Reader position: $pos. Last read tag: $tag.") - pos += length.toLong() - source.skip(length.toLong()) + requireNonNegativeLength(length, tag) + skip(length.toLong()) } STATE_VARINT -> { state = STATE_VARINT @@ -349,44 +348,32 @@ open class ProtoReader(private val source: BufferedSource) { } private fun internalReadVarint32(): Int { - source.require(1) // Throws EOFException if insufficient bytes are available. - pos++ - var tmp = source.readByte() + var tmp = readByte() if (tmp >= 0) { return tmp.toInt() } var result = tmp and 0x7f - source.require(1) // Throws EOFException if insufficient bytes are available. - pos++ - tmp = source.readByte() + tmp = readByte() if (tmp >= 0) { result = result or (tmp shl 7) } else { result = result or (tmp and 0x7f shl 7) - source.require(1) // Throws EOFException if insufficient bytes are available. - pos++ - tmp = source.readByte() + tmp = readByte() if (tmp >= 0) { result = result or (tmp shl 14) } else { result = result or (tmp and 0x7f shl 14) - source.require(1) // Throws EOFException if insufficient bytes are available. - pos++ - tmp = source.readByte() + tmp = readByte() if (tmp >= 0) { result = result or (tmp shl 21) } else { result = result or (tmp and 0x7f shl 21) - source.require(1) // Throws EOFException if insufficient bytes are available. - pos++ - tmp = source.readByte() + tmp = readByte() result = result or (tmp shl 28) if (tmp < 0) { // Discard upper 32 bits. for (i in 0..4) { - source.require(1) // Throws EOFException if insufficient bytes are available. - pos++ - if (source.readByte() >= 0) { + if (readByte() >= 0) { return result } } @@ -407,9 +394,7 @@ open class ProtoReader(private val source: BufferedSource) { var shift = 0 var result: Long = 0 while (shift < 64) { - source.require(1) // Throws EOFException if insufficient bytes are available. - pos++ - val b = source.readByte() + val b = readByte() result = result or ((b and 0x7F).toLong() shl shift) if (b and 0x80 == 0) { afterPackableScalar(STATE_VARINT) @@ -426,6 +411,7 @@ open class ProtoReader(private val source: BufferedSource) { if (state != STATE_FIXED32 && state != STATE_LENGTH_DELIMITED) { throw ProtocolException("Expected FIXED32 or LENGTH_DELIMITED but was $state. Reader position: $pos. Last read tag: $tag.") } + checkedLimit(4) source.require(4) // Throws EOFException if insufficient bytes are available. pos += 4 val result = source.readIntLe() @@ -439,6 +425,7 @@ open class ProtoReader(private val source: BufferedSource) { if (state != STATE_FIXED64 && state != STATE_LENGTH_DELIMITED) { throw ProtocolException("Expected FIXED64 or LENGTH_DELIMITED but was $state. Reader position: $pos. Last read tag: $tag.") } + checkedLimit(8) source.require(8) // Throws EOFException if insufficient bytes are available. pos += 8 val result = source.readLongLe() @@ -469,7 +456,7 @@ open class ProtoReader(private val source: BufferedSource) { if (state != STATE_LENGTH_DELIMITED) { throw ProtocolException("Expected LENGTH_DELIMITED but was $state. Reader position: $pos. Last read tag: $tag.") } - val byteCount = limit - pos + val byteCount = remainingInLimit() source.require(byteCount) // Throws EOFException if insufficient bytes are available. state = STATE_TAG // We've completed a length-delimited scalar. Pop the limit. @@ -524,7 +511,7 @@ open class ProtoReader(private val source: BufferedSource) { */ open fun nextFieldMinLengthInBytes(): Long { return when (nextFieldEncoding) { - FieldEncoding.LENGTH_DELIMITED -> limit - pos + FieldEncoding.LENGTH_DELIMITED -> remainingInLimit() FieldEncoding.FIXED32 -> 4 FieldEncoding.FIXED64 -> 8 FieldEncoding.VARINT -> 1 @@ -532,6 +519,35 @@ open class ProtoReader(private val source: BufferedSource) { } } + private fun skip(byteCount: Long) { + val newPos = checkedLimit(byteCount) + source.skip(byteCount) + pos = newPos + } + + private fun readByte(): Byte { + checkedLimit(1) + source.require(1) // Throws EOFException if insufficient bytes are available. + pos++ + return source.readByte() + } + + private fun requireNonNegativeLength(length: Int, lastReadTag: Int = tag) { + if (length < 0) { + throw ProtocolException("Negative length: $length. Reader position: $pos. Last read tag: $lastReadTag.") + } + } + + private fun checkedLimit(byteCount: Long): Long { + if (byteCount < 0L || byteCount > remainingInLimit()) throw EOFException() + return pos + byteCount + } + + private fun remainingInLimit(): Long { + if (pos > limit) throw EOFException() + return limit - pos + } + companion object { /** The standard number of levels of message nesting to allow. */ internal const val RECURSION_LIMIT = 100 diff --git a/wire-runtime/src/commonTest/kotlin/com/squareup/wire/ProtoReader32Test.kt b/wire-runtime/src/commonTest/kotlin/com/squareup/wire/ProtoReader32Test.kt index 5d0b7e5d8c..2290f528b0 100644 --- a/wire-runtime/src/commonTest/kotlin/com/squareup/wire/ProtoReader32Test.kt +++ b/wire-runtime/src/commonTest/kotlin/com/squareup/wire/ProtoReader32Test.kt @@ -23,6 +23,7 @@ import assertk.assertions.isInstanceOf import com.squareup.wire.ReverseProtoWriterTest.Person import kotlin.test.Test import okio.ByteString.Companion.decodeHex +import okio.EOFException import okio.IOException class ProtoReader32Test { @@ -88,5 +89,40 @@ class ProtoReader32Test { .hasMessage("Negative length: -128. Reader position: 8. Last read tag: 1.") } + /** We had a bug where positive lengths near Int.MAX_VALUE overflowed cursor math. */ + @Test fun lengthDelimitedRejectsPositiveLengthOverflow() { + val knownString = "0affffffff07".decodeHex() + val unknownBytes = "1affffffff07".decodeHex() + val skippedGroup = "0b0affffffff070c".decodeHex() + + assertFailure { + Person.ADAPTER.decode(knownString) + }.isInstanceOf() + + assertFailure { + Person.ADAPTER.decode(unknownBytes) + }.isInstanceOf() + + assertFailure { + Person.ADAPTER.decode(skippedGroup) + }.isInstanceOf() + } + + @Test fun fixed32CannotReadPastLengthDelimitedLimit() { + val encoded = ( + "02" + // varint32 length = 2 + "0d" + // 1: fixed32 + "05000000" // enough bytes in the source, but not in the current message + ).decodeHex() + val reader = ProtoReader32(encoded) + + assertThat(reader.nextLengthDelimited()).isEqualTo(2) + reader.beginMessage() + assertThat(reader.nextTag()).isEqualTo(1) + assertFailure { + reader.readFixed32() + }.isInstanceOf() + } + // Consider pasting new tests into ProtoReaderTest.kt also. } diff --git a/wire-runtime/src/commonTest/kotlin/com/squareup/wire/ProtoReaderTest.kt b/wire-runtime/src/commonTest/kotlin/com/squareup/wire/ProtoReaderTest.kt index e75b572d88..32877112de 100644 --- a/wire-runtime/src/commonTest/kotlin/com/squareup/wire/ProtoReaderTest.kt +++ b/wire-runtime/src/commonTest/kotlin/com/squareup/wire/ProtoReaderTest.kt @@ -24,6 +24,7 @@ import com.squareup.wire.ReverseProtoWriterTest.Person import kotlin.test.Test import okio.Buffer import okio.ByteString.Companion.decodeHex +import okio.EOFException import okio.IOException class ProtoReaderTest { @@ -89,5 +90,21 @@ class ProtoReaderTest { .hasMessage("Negative length: -128. Reader position: 8. Last read tag: 1.") } + @Test fun fixed32CannotReadPastLengthDelimitedLimit() { + val encoded = ( + "02" + // varint32 length = 2 + "0d" + // 1: fixed32 + "05000000" // enough bytes in the source, but not in the current message + ).decodeHex() + val reader = ProtoReader(Buffer().write(encoded)) + + assertThat(reader.nextLengthDelimited()).isEqualTo(2) + reader.beginMessage() + assertThat(reader.nextTag()).isEqualTo(1) + assertFailure { + reader.readFixed32() + }.isInstanceOf() + } + // Consider pasting new tests into ProtoReader32Test.kt also. }