Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions wire-runtime-swift/src/main/swift/ProtoCodable/ProtoReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -788,7 +788,9 @@ public final class ProtoReader {

func readBuffer() throws -> UnsafeRawBufferPointer {
guard case let .lengthDelimited(length) = state else {
fatalError("Decoding field as length delimited when key was not LENGTH_DELIMITED")
throw ProtoDecoder.Error.invalidStructure(
message: "Decoding field as length delimited when key was not LENGTH_DELIMITED"
)
}
state = .tag

Expand All @@ -798,7 +800,9 @@ public final class ProtoReader {
/** Reads a `bytes` field value from the stream. The length is read from the stream prior to the actual data. */
func readData() throws -> Data {
guard case let .lengthDelimited(length) = state else {
fatalError("Decoding field as length delimited when key was not LENGTH_DELIMITED")
throw ProtoDecoder.Error.invalidStructure(
message: "Decoding field as length delimited when key was not LENGTH_DELIMITED"
)
}
state = .tag

Expand All @@ -807,21 +811,33 @@ public final class ProtoReader {

/** Reads a 32-bit little-endian integer from the stream. */
func readFixed32() throws -> UInt32 {
precondition(state == .fixed32 || state == .packedValue)
guard state == .fixed32 || state == .packedValue else {
throw ProtoDecoder.Error.invalidStructure(
message: "Decoding field as fixed32 when key was not FIXED32"
)
}
state = .tag
return try buffer.readFixed32()
}

/** Reads a 64-bit little-endian integer from the stream. */
func readFixed64() throws -> UInt64 {
precondition(state == .fixed64 || state == .packedValue)
guard state == .fixed64 || state == .packedValue else {
throw ProtoDecoder.Error.invalidStructure(
message: "Decoding field as fixed64 when key was not FIXED64"
)
}
state = .tag
return try buffer.readFixed64()
}

/** Reads a raw varint up to 64 bits in length from the stream. */
func readVarint() throws -> UInt64 {
precondition(state == .varint || state == .packedValue)
guard state == .varint || state == .packedValue else {
throw ProtoDecoder.Error.invalidStructure(
message: "Decoding field as varint when key was not VARINT"
)
}
state = .tag

return try buffer.readVarint()
Expand Down
20 changes: 20 additions & 0 deletions wire-runtime-swift/src/test/swift/ProtoDecoderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,26 @@ final class ProtoDecoderTests: XCTestCase {
}
}

func testDecodeRejectsKnownFieldWithMismatchedWireType() throws {
// Person declares field 1 `name` as a string and field 2 `id` as a varint int32.
let payloads = [
"0A0161_1500000000", // id sent as fixed32
"0A0161_110000000000000000", // id sent as fixed64
"0A0161_1200", // id sent as length delimited
"0801", // name sent as varint
"0D00000000", // name sent as fixed32
]

for payload in payloads {
let data = Foundation.Data(hexEncoded: payload)!
XCTAssertThrowsError(try ProtoDecoder().decode(Person.self, from: data), payload) { error in
guard case ProtoDecoder.Error.invalidStructure = error else {
return XCTFail("Unexpected error for \(payload): \(error)")
}
}
}
}

func testDecodeEmptySizeDelimitedData() throws {
let decoder = ProtoDecoder()
let object = try decoder.decodeSizeDelimited(SimpleOptional2.self, from: Foundation.Data())
Expand Down
93 changes: 93 additions & 0 deletions wire-runtime-swift/src/test/swift/ProtoReaderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,91 @@ final class ProtoReaderTests: XCTestCase {
}
}

func testReadBufferRejectsMismatchedWireType() throws {
let data = Foundation.Data(hexEncoded: """
08 // (Tag 1 | Varint)
01 // Value 1
""")!

XCTAssertThrowsError(
try test(data: data) { reader in
_ = try reader.forEachTag { _ in
_ = try reader.readBuffer()
}
}
) { error in
assertInvalidStructureError(error, message: "Decoding field as length delimited when key was not LENGTH_DELIMITED")
}
}

func testReadDataRejectsMismatchedWireType() throws {
let data = Foundation.Data(hexEncoded: """
0D // (Tag 1 | Fixed32)
00000000 // Value 0
""")!

XCTAssertThrowsError(
try test(data: data) { reader in
_ = try reader.forEachTag { _ in
_ = try reader.readData()
}
}
) { error in
assertInvalidStructureError(error, message: "Decoding field as length delimited when key was not LENGTH_DELIMITED")
}
}

func testReadFixed32RejectsMismatchedWireType() throws {
let data = Foundation.Data(hexEncoded: """
08 // (Tag 1 | Varint)
01 // Value 1
""")!

XCTAssertThrowsError(
try test(data: data) { reader in
_ = try reader.forEachTag { _ in
_ = try reader.readFixed32()
}
}
) { error in
assertInvalidStructureError(error, message: "Decoding field as fixed32 when key was not FIXED32")
}
}

func testReadFixed64RejectsMismatchedWireType() throws {
let data = Foundation.Data(hexEncoded: """
0D // (Tag 1 | Fixed32)
00000000 // Value 0
""")!

XCTAssertThrowsError(
try test(data: data) { reader in
_ = try reader.forEachTag { _ in
_ = try reader.readFixed64()
}
}
) { error in
assertInvalidStructureError(error, message: "Decoding field as fixed64 when key was not FIXED64")
}
}

func testReadVarintRejectsMismatchedWireType() throws {
let data = Foundation.Data(hexEncoded: """
0A // (Tag 1 | Length Delimited)
00 // Length 0
""")!

XCTAssertThrowsError(
try test(data: data) { reader in
_ = try reader.forEachTag { _ in
_ = try reader.readVarint()
}
}
) { error in
assertInvalidStructureError(error, message: "Decoding field as varint when key was not VARINT")
}
}

func testNestedMessageRejectsOversizedLength() throws {
let data = Foundation.Data(hexEncoded: """
12 // (Tag 2 | Length Delimited)
Expand Down Expand Up @@ -1520,6 +1605,14 @@ final class ProtoReaderTests: XCTestCase {
XCTAssertEqual(message, "Negative length: -128. Reader position: \(readerPosition). Last read tag: 1.")
}

private func assertInvalidStructureError(_ error: Error, message expectedMessage: String) {
guard case let ProtoDecoder.Error.invalidStructure(message) = error else {
XCTFail("Unexpected error: \(error)")
return
}
XCTAssertEqual(message, expectedMessage)
}

private func assertUnexpectedEndError(_ error: Error) {
guard case ProtoDecoder.Error.unexpectedEndOfData = error else {
XCTFail("Unexpected error: \(error)")
Expand Down
Loading