forked from haxii/socks5
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproxy_stream_test.go
More file actions
313 lines (284 loc) · 8.14 KB
/
Copy pathproxy_stream_test.go
File metadata and controls
313 lines (284 loc) · 8.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
package socks5
import (
"bytes"
"crypto/rand"
"errors"
"io"
"net"
"strings"
"testing"
"testing/iotest"
"time"
)
func TestProxyStream_SmallData(t *testing.T) {
src := bytes.NewReader([]byte("hello world"))
var dst bytes.Buffer
if err := ProxyStream(src, &dst); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if dst.String() != "hello world" {
t.Fatalf("got %q, want %q", dst.String(), "hello world")
}
}
func TestProxyStream_LargeData(t *testing.T) {
data := make([]byte, 1<<20) // 1 MiB, much larger than the 64 KiB ring buffer
if _, err := rand.Read(data); err != nil {
t.Fatal(err)
}
src := bytes.NewReader(data)
var dst bytes.Buffer
if err := ProxyStream(src, &dst); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !bytes.Equal(dst.Bytes(), data) {
t.Fatalf("data mismatch: got %d bytes, want %d", dst.Len(), len(data))
}
}
func TestProxyStream_Empty(t *testing.T) {
src := bytes.NewReader(nil)
var dst bytes.Buffer
if err := ProxyStream(src, &dst); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if dst.Len() != 0 {
t.Fatalf("expected empty, got %d bytes", dst.Len())
}
}
func TestProxyStream_ExactBufferSize(t *testing.T) {
// Exactly 64 KiB — boundary for the ring buffer.
data := make([]byte, 64<<10)
for i := range data {
data[i] = byte(i)
}
src := bytes.NewReader(data)
var dst bytes.Buffer
if err := ProxyStream(src, &dst); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !bytes.Equal(dst.Bytes(), data) {
t.Fatalf("data mismatch")
}
}
// TestProxyStream_OneByteReader forces the reader to deliver data one byte
// at a time, which aggressively exercises the ring buffer's wrap-around:
// head advances by 1 on every iteration, so the reader repeatedly crosses
// the bufSize boundary.
func TestProxyStream_OneByteReader(t *testing.T) {
data := make([]byte, 200<<10) // enough to wrap several times
for i := range data {
data[i] = byte(i)
}
src := iotest.OneByteReader(bytes.NewReader(data))
var dst bytes.Buffer
if err := ProxyStream(src, &dst); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !bytes.Equal(dst.Bytes(), data) {
t.Fatalf("data mismatch: got %d bytes, want %d", dst.Len(), len(data))
}
}
// TestProxyStream_HalfReader exercises wrap-around with a different stride —
// each Read returns only half of the requested slice, so head advances in
// irregular chunks relative to the buffer boundary.
func TestProxyStream_HalfReader(t *testing.T) {
data := make([]byte, 200<<10)
for i := range data {
data[i] = byte(i * 3)
}
src := iotest.HalfReader(bytes.NewReader(data))
var dst bytes.Buffer
if err := ProxyStream(src, &dst); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !bytes.Equal(dst.Bytes(), data) {
t.Fatalf("data mismatch: got %d bytes, want %d", dst.Len(), len(data))
}
}
// TestProxyStream_DataErrReader verifies we handle the io.Reader contract
// variant where Read returns the final bytes together with a non-nil error
// on the same call, rather than on a subsequent call.
func TestProxyStream_DataErrReader(t *testing.T) {
data := []byte("hello, data+err reader")
src := iotest.DataErrReader(bytes.NewReader(data))
var dst bytes.Buffer
if err := ProxyStream(src, &dst); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !bytes.Equal(dst.Bytes(), data) {
t.Fatalf("got %q, want %q", dst.String(), data)
}
}
// slowWriter accepts only one byte per call, stressing the writer side of
// the ring buffer (repeated partial drains, re-computing the contiguous
// readable segment on every iteration).
type slowWriter struct {
buf bytes.Buffer
}
func (s *slowWriter) Write(p []byte) (int, error) {
if len(p) > 1 {
return s.buf.Write(p[:1])
}
return s.buf.Write(p)
}
func TestProxyStream_SlowWriter(t *testing.T) {
data := make([]byte, 256<<10)
for i := range data {
data[i] = byte(i)
}
src := bytes.NewReader(data)
dst := &slowWriter{}
if err := ProxyStream(src, dst); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !bytes.Equal(dst.buf.Bytes(), data) {
t.Fatalf("data mismatch: got %d bytes, want %d", dst.buf.Len(), len(data))
}
}
// errWriter fails after n bytes have been written. Used to test the write
// error propagation path. iotest.TruncateWriter silently drops data without
// returning an error, so it doesn't fit here.
type errWriter struct {
n int
buf bytes.Buffer
}
func (e *errWriter) Write(p []byte) (int, error) {
if e.buf.Len()+len(p) > e.n {
remaining := e.n - e.buf.Len()
if remaining > 0 {
e.buf.Write(p[:remaining])
return remaining, errors.New("write limit reached")
}
return 0, errors.New("write limit reached")
}
return e.buf.Write(p)
}
func TestProxyStream_WriteError(t *testing.T) {
data := make([]byte, 128<<10)
src := bytes.NewReader(data)
dst := &errWriter{n: 1000}
err := ProxyStream(src, dst)
if err == nil {
t.Fatal("expected error, got nil")
}
if !strings.Contains(err.Error(), "write error") {
t.Fatalf("expected write error, got: %v", err)
}
}
func TestProxyStream_ReadError(t *testing.T) {
boom := errors.New("boom")
src := iotest.ErrReader(boom)
var dst bytes.Buffer
err := ProxyStream(src, &dst)
if err == nil {
t.Fatal("expected error, got nil")
}
if !strings.Contains(err.Error(), "read error") || !strings.Contains(err.Error(), "boom") {
t.Fatalf("expected wrapped boom, got: %v", err)
}
}
func TestProxyStream_TCPPair(t *testing.T) {
// End-to-end over real TCP: exercises the closeReader/closeWriter paths.
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer ln.Close()
data := make([]byte, 256<<10)
for i := range data {
data[i] = byte(i)
}
srvErr := make(chan error, 1)
go func() {
conn, err := ln.Accept()
if err != nil {
srvErr <- err
return
}
defer conn.Close()
if _, err := conn.Write(data); err != nil {
srvErr <- err
return
}
srvErr <- conn.(*net.TCPConn).CloseWrite()
}()
conn, err := net.Dial("tcp", ln.Addr().String())
if err != nil {
t.Fatal(err)
}
defer conn.Close()
var dst bytes.Buffer
if err := ProxyStream(conn, &dst); err != nil {
t.Fatalf("ProxyStream: %v", err)
}
if !bytes.Equal(dst.Bytes(), data) {
t.Fatalf("data mismatch: got %d bytes, want %d", dst.Len(), len(data))
}
if err := <-srvErr; err != nil {
t.Fatalf("server error: %v", err)
}
}
// plainReader/plainWriter hide the WriteTo/ReadFrom fast paths so io.Copy
// actually goes through its internal buffer — an apples-to-apples baseline.
type plainReader struct{ io.Reader }
type plainWriter struct{ io.Writer }
// delayWriter and delayReader model a slow peer. This is the scenario
// ProxyStream is designed for: the reader keeps filling the ring buffer
// in parallel with a blocked writer (and vice versa).
type delayWriter struct {
w io.Writer
delay time.Duration
}
func (d *delayWriter) Write(p []byte) (int, error) {
time.Sleep(d.delay)
return d.w.Write(p)
}
type delayReader struct {
r io.Reader
delay time.Duration
}
func (d *delayReader) Read(p []byte) (int, error) {
time.Sleep(d.delay)
return d.r.Read(p)
}
func BenchmarkProxyStream_Fast(b *testing.B) {
data := make([]byte, 1<<20)
b.SetBytes(int64(len(data)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
src := plainReader{bytes.NewReader(data)}
ProxyStream(src, plainWriter{io.Discard})
}
}
func BenchmarkIOCopy_Fast(b *testing.B) {
data := make([]byte, 1<<20)
b.SetBytes(int64(len(data)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
src := plainReader{bytes.NewReader(data)}
io.Copy(plainWriter{io.Discard}, src)
}
}
func BenchmarkProxyStream_SlowWrite(b *testing.B) {
data := make([]byte, 1<<20)
b.SetBytes(int64(len(data)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
src := &delayReader{r: bytes.NewReader(data), delay: 50 * time.Microsecond}
dst := &delayWriter{w: io.Discard, delay: 50 * time.Microsecond}
ProxyStream(src, dst)
}
}
func BenchmarkIOCopy_SlowWrite(b *testing.B) {
data := make([]byte, 1<<20)
b.SetBytes(int64(len(data)))
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
src := &delayReader{r: bytes.NewReader(data), delay: 50 * time.Microsecond}
dst := &delayWriter{w: io.Discard, delay: 50 * time.Microsecond}
io.Copy(dst, src)
}
}