From f6154167f5d71215f85f446a5e70dcce09d8ada7 Mon Sep 17 00:00:00 2001 From: Joannis Orlandos Date: Sat, 22 Mar 2025 15:45:21 +0100 Subject: [PATCH 1/2] Add support for decoding null bulk strings --- Sources/RESP3/RESP3Token.swift | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Sources/RESP3/RESP3Token.swift b/Sources/RESP3/RESP3Token.swift index 9a0aa891..9876f092 100644 --- a/Sources/RESP3/RESP3Token.swift +++ b/Sources/RESP3/RESP3Token.swift @@ -84,7 +84,7 @@ public struct RESP3Token: Hashable, Sendable { case number(Int64) case double(Double) case boolean(Bool) - case null + case null, nullBulkString case bigNumber(ByteBuffer) case array(Array) case attribute(Map) @@ -109,6 +109,11 @@ public struct RESP3Token: Hashable, Sendable { var lengthSlice = try! local.readCRLFTerminatedSlice2()! let lengthString = lengthSlice.readString(length: lengthSlice.readableBytes)! let length = Int(lengthString)! + + if length < 0 { + return .nullBulkString + } + return .blobString(local.readSlice(length: length)!) case .blobError: @@ -310,6 +315,23 @@ extension ByteBuffer { throw RESP3ParsingError(code: .canNotParseInteger, buffer: self) } + // blob length is negative, this may be a null bulk string + if blobLength < 0 { + // null bulk string is -1 + guard blobLength == -1 else { + // -1 is the + throw RESP3ParsingError(code: .invalidData, buffer: self) + } + + guard self.getInteger(at: self.readerIndex, as: UInt16.self) == .crlf else { + throw RESP3ParsingError(code: .invalidData, buffer: self) + } + + // null bulk string is 1 byte for the $ prefix, 2 bytes for the ss-1, and 2 bytes for the \r\n + let respLength = 1 + 2 + 2 + return self.readSlice(length: respLength)! + } + let respLength = 1 + lengthLineLength + blobLength + 2 guard let slice = self.readSlice(length: respLength) else { From 9eddf597019088c156277dd6aac639be54876e0c Mon Sep 17 00:00:00 2001 From: Joannis Orlandos Date: Mon, 24 Mar 2025 21:06:02 +0100 Subject: [PATCH 2/2] Join all null-types and support null arrays --- Sources/RESP3/RESP3Token.swift | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/Sources/RESP3/RESP3Token.swift b/Sources/RESP3/RESP3Token.swift index 9876f092..4bac5a8f 100644 --- a/Sources/RESP3/RESP3Token.swift +++ b/Sources/RESP3/RESP3Token.swift @@ -84,7 +84,7 @@ public struct RESP3Token: Hashable, Sendable { case number(Int64) case double(Double) case boolean(Bool) - case null, nullBulkString + case null case bigNumber(ByteBuffer) case array(Array) case attribute(Map) @@ -111,7 +111,7 @@ public struct RESP3Token: Hashable, Sendable { let length = Int(lengthString)! if length < 0 { - return .nullBulkString + return .null } return .blobString(local.readSlice(length: length)!) @@ -134,6 +134,11 @@ public struct RESP3Token: Hashable, Sendable { var countSlice = try! local.readCRLFTerminatedSlice2()! let countString = countSlice.readString(length: countSlice.readableBytes)! let count = Int(countString)! + + if count < 0 { + return .null + } + return .array(.init(count: count, buffer: local)) case .push: @@ -319,7 +324,7 @@ extension ByteBuffer { if blobLength < 0 { // null bulk string is -1 guard blobLength == -1 else { - // -1 is the + // -1 is the only supported negative value throw RESP3ParsingError(code: .invalidData, buffer: self) } @@ -327,7 +332,7 @@ extension ByteBuffer { throw RESP3ParsingError(code: .invalidData, buffer: self) } - // null bulk string is 1 byte for the $ prefix, 2 bytes for the ss-1, and 2 bytes for the \r\n + // null bulk string is 1 byte for the $ prefix, 2 bytes for the -1, and 2 bytes for the \r\n let respLength = 1 + 2 + 2 return self.readSlice(length: respLength)! } @@ -393,6 +398,20 @@ extension ByteBuffer { localCopy.moveReaderIndex(forwardBy: prefixLength) let elementCount = arrayLength * multiplier + if elementCount < 0 { + guard elementCount == -1, marker == .array else { + // -1 is the only supported negative value + throw RESP3ParsingError(code: .invalidData, buffer: self) + } + + guard self.getInteger(at: self.readerIndex, as: UInt16.self) == .crlf else { + throw RESP3ParsingError(code: .invalidData, buffer: self) + } + + // null bulk string is 1 byte for the * prefix, 2 bytes for the -1, and 2 bytes for the \r\n + let respLength = 1 + 2 + 2 + return self.readSlice(length: respLength)! + } func iterateChildren(consuming localCopy: inout ByteBuffer, count: Int, depth: Int) throws -> Int? { var bodyLength = 0