Skip to content

Commit 5254560

Browse files
committed
added test to nextcommand reader
1 parent d8057d9 commit 5254560

File tree

2 files changed

+123
-18
lines changed

2 files changed

+123
-18
lines changed

append.go

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ const (
1818
)
1919

2020
var errInvalidMessage = &errProtocol{"invalid message"}
21+
var errIncompletePacket = &errProtocol{"incomplete packet"}
2122

2223
// ReadNextCommand reads the next command from the provided packet. It's
23-
// possibel that the packet contains multiple commands, or zero commands
24+
// possible that the packet contains multiple commands, or zero commands
2425
// when the packet is incomplete.
2526
// 'args' is an optional reusable buffer and it can be nil.
2627
// 'argsout' are the output arguments for the command. 'kind' is the type of
@@ -31,50 +32,58 @@ var errInvalidMessage = &errProtocol{"invalid message"}
3132
func ReadNextCommand(packet []byte, args [][]byte) (
3233
leftover []byte, argsout [][]byte, kind Kind, stop bool, err error,
3334
) {
35+
args = args[:0]
3436
if len(packet) > 0 {
3537
if packet[0] != '*' {
3638
if packet[0] == '$' {
3739
return readTile38Command(packet, args)
3840
}
3941
return readTelnetCommand(packet, args)
4042
}
41-
for i := 1; i < len(packet); i++ {
43+
// standard redis command
44+
for s, i := 1, 1; i < len(packet); i++ {
4245
if packet[i] == '\n' {
4346
if packet[i-1] != '\r' {
44-
return packet, args, Redis, true, errInvalidMultiBulkLength
47+
println("here", i)
48+
return packet, args[:0], Redis, true, errInvalidMultiBulkLength
4549
}
46-
count, ok := parseInt(packet[1 : i-1])
47-
if !ok || count <= 0 {
48-
return packet, args, Redis, true, errInvalidMultiBulkLength
50+
count, ok := parseInt(packet[s : i-1])
51+
if !ok || count < 0 {
52+
println("there", i, count, ok, "["+string(packet[s:i-1])+"]")
53+
return packet, args[:0], Redis, true, errInvalidMultiBulkLength
4954
}
5055
i++
56+
if count == 0 {
57+
return packet[i:], args[:0], Redis, i == len(packet), nil
58+
}
5159
nextArg:
5260
for j := 0; j < count; j++ {
5361
if i == len(packet) {
5462
break
5563
}
5664
if packet[i] != '$' {
57-
return packet, args, Redis, true,
65+
return packet, args[:0], Redis, true,
5866
&errProtocol{"expected '$', got '" +
5967
string(packet[i]) + "'"}
6068
}
6169
for s := i + 1; i < len(packet); i++ {
6270
if packet[i] == '\n' {
6371
if packet[i-1] != '\r' {
64-
return packet, args, Redis, true, errInvalidBulkLength
72+
return packet, args[:0], Redis, true, errInvalidBulkLength
6573
}
6674
n, ok := parseInt(packet[s : i-1])
6775
if !ok || count <= 0 {
68-
return packet, args, Redis, true, errInvalidBulkLength
76+
return packet, args[:0], Redis, true, errInvalidBulkLength
6977
}
7078
i++
7179
if len(packet)-i >= n+2 {
7280
if packet[i+n] != '\r' || packet[i+n+1] != '\n' {
73-
return packet, args, Redis, true, errInvalidBulkLength
81+
return packet, args[:0], Redis, true, errInvalidBulkLength
7482
}
7583
args = append(args, packet[i:i+n])
7684
i += n + 2
7785
if j == count-1 {
86+
// done reading
7887
return packet[i:], args, Redis, i == len(packet), nil
7988
}
8089
continue nextArg
@@ -88,7 +97,7 @@ func ReadNextCommand(packet []byte, args [][]byte) (
8897
}
8998
}
9099
}
91-
return packet, args, Redis, true, nil
100+
return packet, args[:0], Redis, true, errIncompletePacket
92101
}
93102

94103
func readTile38Command(b []byte, argsbuf [][]byte) (
@@ -98,12 +107,12 @@ func readTile38Command(b []byte, argsbuf [][]byte) (
98107
if b[i] == ' ' {
99108
n, ok := parseInt(b[1:i])
100109
if !ok || n < 0 {
101-
return b, args, Tile38, true, errInvalidMessage
110+
return b, args[:0], Tile38, true, errInvalidMessage
102111
}
103112
i++
104113
if len(b) >= i+n+2 {
105114
if b[i+n] != '\r' || b[i+n+1] != '\n' {
106-
return b, args, Tile38, true, errInvalidMessage
115+
return b, args[:0], Tile38, true, errInvalidMessage
107116
}
108117
line := b[i : i+n]
109118
reading:
@@ -143,7 +152,7 @@ func readTile38Command(b []byte, argsbuf [][]byte) (
143152
break
144153
}
145154
}
146-
return b, args, Tile38, true, nil
155+
return b, args[:0], Tile38, true, errIncompletePacket
147156
}
148157
func readTelnetCommand(b []byte, argsbuf [][]byte) (
149158
leftover []byte, args [][]byte, kind Kind, stop bool, err error,
@@ -175,7 +184,7 @@ func readTelnetCommand(b []byte, argsbuf [][]byte) (
175184
}
176185
if c == '"' || c == '\'' {
177186
if i != 0 {
178-
return b, args, Telnet, true, errUnbalancedQuotes
187+
return b, args[:0], Telnet, true, errUnbalancedQuotes
179188
}
180189
quotech = c
181190
quote = true
@@ -199,7 +208,7 @@ func readTelnetCommand(b []byte, argsbuf [][]byte) (
199208
args = append(args, nline)
200209
line = line[i+1:]
201210
if len(line) > 0 && line[0] != ' ' {
202-
return b, args, Telnet, true, errUnbalancedQuotes
211+
return b, args[:0], Telnet, true, errUnbalancedQuotes
203212
}
204213
continue outer
205214
} else if c == '\\' {
@@ -210,7 +219,7 @@ func readTelnetCommand(b []byte, argsbuf [][]byte) (
210219
nline = append(nline, c)
211220
}
212221
if quote {
213-
return b, args, Telnet, true, errUnbalancedQuotes
222+
return b, args[:0], Telnet, true, errUnbalancedQuotes
214223
}
215224
if len(line) > 0 {
216225
args = append(args, line)
@@ -220,7 +229,7 @@ func readTelnetCommand(b []byte, argsbuf [][]byte) (
220229
return b[i+1:], args, Telnet, i == len(b), nil
221230
}
222231
}
223-
return b, args, Telnet, true, nil
232+
return b, args[:0], Telnet, true, errIncompletePacket
224233
}
225234

226235
// AppendUint appends a Redis protocol uint64 to the input bytes.

append_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package redcon
2+
3+
import (
4+
"bytes"
5+
"math/rand"
6+
"testing"
7+
"time"
8+
)
9+
10+
func TestNextCommand(t *testing.T) {
11+
rand.Seed(time.Now().UnixNano())
12+
start := time.Now()
13+
for time.Since(start) < time.Second {
14+
// keep copy of pipeline args for final compare
15+
var plargs [][][]byte
16+
17+
// create a pipeline of random number of commands with random data.
18+
N := rand.Int() % 10000
19+
var data []byte
20+
for i := 0; i < N; i++ {
21+
nargs := rand.Int() % 10
22+
data = AppendArray(data, nargs)
23+
var args [][]byte
24+
for j := 0; j < nargs; j++ {
25+
arg := make([]byte, rand.Int()%100)
26+
if _, err := rand.Read(arg); err != nil {
27+
t.Fatal(err)
28+
}
29+
data = AppendBulk(data, arg)
30+
args = append(args, arg)
31+
}
32+
plargs = append(plargs, args)
33+
}
34+
35+
// break data into random number of chunks
36+
chunkn := rand.Int() % 100
37+
if chunkn == 0 {
38+
chunkn = 1
39+
}
40+
if len(data) < chunkn {
41+
continue
42+
}
43+
var chunks [][]byte
44+
var chunksz int
45+
for i := 0; i < len(data); i += chunksz {
46+
chunksz = rand.Int() % (len(data) / chunkn)
47+
var chunk []byte
48+
if i+chunksz < len(data) {
49+
chunk = data[i : i+chunksz]
50+
} else {
51+
chunk = data[i:]
52+
}
53+
chunks = append(chunks, chunk)
54+
}
55+
56+
// process chunks
57+
var rbuf []byte
58+
var fargs [][][]byte
59+
for _, chunk := range chunks {
60+
var data []byte
61+
if len(rbuf) > 0 {
62+
data = append(rbuf, chunk...)
63+
} else {
64+
data = chunk
65+
}
66+
for {
67+
leftover, args, _, stop, err := ReadNextCommand(data, nil)
68+
data = leftover
69+
if err != nil && err != errIncompletePacket {
70+
t.Fatal(err)
71+
}
72+
if err != errIncompletePacket {
73+
fargs = append(fargs, args)
74+
}
75+
if stop {
76+
break
77+
}
78+
}
79+
rbuf = append(rbuf[:0], data...)
80+
}
81+
// compare final args to original
82+
if len(plargs) != len(fargs) {
83+
t.Fatalf("not equal size: %v != %v", len(plargs), len(fargs))
84+
}
85+
for i := 0; i < len(plargs); i++ {
86+
if len(plargs[i]) != len(fargs[i]) {
87+
t.Fatalf("not equal size for item %v: %v != %v", i, len(plargs[i]), len(fargs[i]))
88+
}
89+
for j := 0; j < len(plargs[i]); j++ {
90+
if !bytes.Equal(plargs[i][j], plargs[i][j]) {
91+
t.Fatalf("not equal for item %v:%v: %v != %v", i, j, len(plargs[i][j]), len(fargs[i][j]))
92+
}
93+
}
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)