1
1
import Foundation
2
2
import NIO
3
3
4
- /// Handles incoming byte messages from Redis and decodes them according to the RESP protocol.
5
- ///
6
- /// See: https://redis.io/topics/protocol
7
- internal final class RESPDecoder : ByteToMessageDecoder {
8
- /// `ByteToMessageDecoder`
9
- public typealias InboundOut = RESPValue
10
-
11
- /// See `ByteToMessageDecoder.decode(ctx:buffer:)`
12
- func decode( ctx: ChannelHandlerContext , buffer: inout ByteBuffer ) throws -> DecodingState {
13
- var position = 0
14
-
15
- switch try _parse ( at: & position, from: & buffer) {
16
- case . notYetParsed:
17
- return . needMoreData
18
-
19
- case . parsed( let RESPValue) :
20
- ctx. fireChannelRead ( wrapInboundOut ( RESPValue) )
21
- buffer. moveReaderIndex ( forwardBy: position)
22
- return . continue
23
- }
24
- }
25
- }
26
-
27
- // MARK: RESP Parsing
28
-
29
4
extension UInt8 {
30
5
static let newline : UInt8 = 0xA
31
6
static let carriageReturn : UInt8 = 0xD
@@ -36,13 +11,39 @@ extension UInt8 {
36
11
static let colon : UInt8 = 0x3A
37
12
}
38
13
39
- extension RESPDecoder {
40
- enum _RESPValueDecodingState {
14
+ private extension ByteBuffer {
15
+ /// Copies bytes from the `ByteBuffer` from at the provided position, up to the length desired.
16
+ ///
17
+ /// buffer.copyBytes(at: 3, length: 2)
18
+ /// // Optional(2 bytes), assuming buffer contains 5 bytes
19
+ ///
20
+ /// - Parameters:
21
+ /// - at: The position offset to copy bytes from the buffer, defaulting to `0`.
22
+ /// - length: The number of bytes to copy.
23
+ func copyBytes( at offset: Int = 0 , length: Int ) -> [ UInt8 ] ? {
24
+ guard readableBytes >= offset + length else { return nil }
25
+ return getBytes ( at: offset + readerIndex, length: length)
26
+ }
27
+ }
28
+
29
+ /// Handles incoming byte messages from Redis and decodes them according to the RESP protocol.
30
+ ///
31
+ /// See: https://redis.io/topics/protocol
32
+ public final class RESPDecoder {
33
+ /// Representation of a `RESPDecoder.parse(at:from:) result, with either a decoded `RESPValue` or an indicator
34
+ /// that the buffer contains an incomplete RESP message from the position provided.
35
+ public enum ParsingState {
41
36
case notYetParsed
42
37
case parsed( RESPValue )
43
38
}
44
39
45
- func _parse( at position: inout Int , from buffer: inout ByteBuffer ) throws -> _RESPValueDecodingState {
40
+ /// Attempts to parse the `ByteBuffer`, starting at the specified position, following the RESP specification.
41
+ ///
42
+ /// See https://redis.io/topics/protocol
43
+ /// - Parameters:
44
+ /// - at: The index of the buffer that should be considered the "front" to begin message parsing.
45
+ /// - from: The buffer that contains the bytes that need to be decoded.
46
+ public func parse( at position: inout Int , from buffer: inout ByteBuffer ) throws -> ParsingState {
46
47
guard let token = buffer. copyBytes ( at: position, length: 1 ) ? . first else { return . notYetParsed }
47
48
48
49
position += 1
@@ -120,7 +121,7 @@ extension RESPDecoder {
120
121
}
121
122
122
123
/// See https://redis.io/topics/protocol#resp-bulk-strings
123
- func _parseBulkString( at position: inout Int , from buffer: inout ByteBuffer ) throws -> _RESPValueDecodingState {
124
+ func _parseBulkString( at position: inout Int , from buffer: inout ByteBuffer ) throws -> ParsingState {
124
125
guard let size = try _parseInteger ( at: & position, from: & buffer) else { return . notYetParsed }
125
126
126
127
// Redis sends '-1' to represent a null string
@@ -153,16 +154,16 @@ extension RESPDecoder {
153
154
}
154
155
155
156
/// See https://redis.io/topics/protocol#resp-arrays
156
- func _parseArray( at position: inout Int , from buffer: inout ByteBuffer ) throws -> _RESPValueDecodingState {
157
+ func _parseArray( at position: inout Int , from buffer: inout ByteBuffer ) throws -> ParsingState {
157
158
guard let arraySize = try _parseInteger ( at: & position, from: & buffer) else { return . notYetParsed }
158
159
guard arraySize > - 1 else { return . parsed( . null) }
159
160
guard arraySize > 0 else { return . parsed( . array( [ ] ) ) }
160
161
161
- var array = [ _RESPValueDecodingState ] ( repeating: . notYetParsed, count: arraySize)
162
+ var array = [ ParsingState ] ( repeating: . notYetParsed, count: arraySize)
162
163
for index in 0 ..< arraySize {
163
164
guard buffer. readableBytes - position > 0 else { return . notYetParsed }
164
165
165
- let parseResult = try _parse ( at: & position, from: & buffer)
166
+ let parseResult = try parse ( at: & position, from: & buffer)
166
167
switch parseResult {
167
168
case . parsed:
168
169
array [ index] = parseResult
@@ -184,17 +185,22 @@ extension RESPDecoder {
184
185
}
185
186
}
186
187
187
- private extension ByteBuffer {
188
- /// Copies bytes from the `ByteBuffer` from at the provided position, up to the length desired.
189
- ///
190
- /// buffer.copyBytes(at: 3, length: 2)
191
- /// // Optional(2 bytes), assuming buffer contains 5 bytes
192
- ///
193
- /// - Parameters:
194
- /// - at: The position offset to copy bytes from the buffer, defaulting to `0`.
195
- /// - length: The number of bytes to copy.
196
- func copyBytes( at offset: Int = 0 , length: Int ) -> [ UInt8 ] ? {
197
- guard readableBytes >= offset + length else { return nil }
198
- return getBytes ( at: offset + readerIndex, length: length)
188
+ extension RESPDecoder : ByteToMessageDecoder {
189
+ /// `ByteToMessageDecoder.InboundOut`
190
+ public typealias InboundOut = RESPValue
191
+
192
+ /// See `ByteToMessageDecoder.decode(ctx:buffer:)`
193
+ public func decode( ctx: ChannelHandlerContext , buffer: inout ByteBuffer ) throws -> DecodingState {
194
+ var position = 0
195
+
196
+ switch try parse ( at: & position, from: & buffer) {
197
+ case . notYetParsed:
198
+ return . needMoreData
199
+
200
+ case . parsed( let RESPValue) :
201
+ ctx. fireChannelRead ( wrapInboundOut ( RESPValue) )
202
+ buffer. moveReaderIndex ( forwardBy: position)
203
+ return . continue
204
+ }
199
205
}
200
206
}
0 commit comments