@@ -21,11 +21,17 @@ import (
21
21
"fmt"
22
22
"io"
23
23
"sync"
24
+
25
+ "github.com/Jigsaw-Code/outline-ss-server/slicepool"
24
26
)
25
27
26
28
// payloadSizeMask is the maximum size of payload in bytes.
27
29
const payloadSizeMask = 0x3FFF // 16*1024 - 1
28
30
31
+ // Buffer pool used for decrypting Shadowsocks streams.
32
+ // The largest buffer we could need is for decrypting a max-length payload.
33
+ var readBufPool = slicepool .MakePool (payloadSizeMask + maxTagSize ())
34
+
29
35
// Writer is an io.Writer that also implements io.ReaderFrom to
30
36
// allow for piping the data without extra allocations and copies.
31
37
// The LazyWrite and Flush methods allow a header to be
@@ -262,7 +268,10 @@ type chunkReader struct {
262
268
aead cipher.AEAD
263
269
// Index of the next encrypted chunk to read.
264
270
counter []byte
265
- buf []byte
271
+ // Buffer for the uint16 size and its AEAD tag. Made in init().
272
+ payloadSizeBuf []byte
273
+ // Holds a buffer for the payload and its AEAD tag, when needed.
274
+ payload slicepool.LazySlice
266
275
}
267
276
268
277
// Reader is an io.Reader that also implements io.WriterTo to
@@ -276,7 +285,11 @@ type Reader interface {
276
285
// the shadowsocks protocol with the given shadowsocks cipher.
277
286
func NewShadowsocksReader (reader io.Reader , ssCipher * Cipher ) Reader {
278
287
return & readConverter {
279
- cr : & chunkReader {reader : reader , ssCipher : ssCipher },
288
+ cr : & chunkReader {
289
+ reader : reader ,
290
+ ssCipher : ssCipher ,
291
+ payload : readBufPool .LazySlice (),
292
+ },
280
293
}
281
294
}
282
295
@@ -296,7 +309,7 @@ func (cr *chunkReader) init() (err error) {
296
309
return fmt .Errorf ("failed to create AEAD: %v" , err )
297
310
}
298
311
cr .counter = make ([]byte , cr .aead .NonceSize ())
299
- cr .buf = make ([]byte , payloadSizeMask + cr .aead .Overhead ())
312
+ cr .payloadSizeBuf = make ([]byte , 2 + cr .aead .Overhead ())
300
313
}
301
314
return nil
302
315
}
@@ -318,31 +331,38 @@ func (cr *chunkReader) readMessage(buf []byte) error {
318
331
return nil
319
332
}
320
333
334
+ // ReadChunk returns the next chunk from the stream. Callers must fully
335
+ // consume and discard the previous chunk before calling ReadChunk again.
321
336
func (cr * chunkReader ) ReadChunk () ([]byte , error ) {
322
337
if err := cr .init (); err != nil {
323
338
return nil , err
324
339
}
340
+
341
+ // Release the previous payload buffer.
342
+ cr .payload .Release ()
343
+
325
344
// In Shadowsocks-AEAD, each chunk consists of two
326
345
// encrypted messages. The first message contains the payload length,
327
- // and the second message is the payload.
328
- sizeBuf := cr . buf [: 2 + cr . aead . Overhead ()]
329
- if err := cr .readMessage (sizeBuf ); err != nil {
346
+ // and the second message is the payload. Idle read threads will
347
+ // block here until the next chunk.
348
+ if err := cr .readMessage (cr . payloadSizeBuf ); err != nil {
330
349
if err != io .EOF && err != io .ErrUnexpectedEOF {
331
350
err = fmt .Errorf ("failed to read payload size: %v" , err )
332
351
}
333
352
return nil , err
334
353
}
335
- size := int (binary .BigEndian .Uint16 (sizeBuf ) & payloadSizeMask )
354
+ size := int (binary .BigEndian .Uint16 (cr . payloadSizeBuf ) & payloadSizeMask )
336
355
sizeWithTag := size + cr .aead .Overhead ()
337
- if cap (cr .buf ) < sizeWithTag {
338
- // This code is unreachable.
356
+ payloadBuf := cr .payload .Acquire ()
357
+ if cap (payloadBuf ) < sizeWithTag {
358
+ // This code is unreachable if the constants are set correctly.
339
359
return nil , io .ErrShortBuffer
340
360
}
341
- payloadBuf := cr .buf [:sizeWithTag ]
342
- if err := cr .readMessage (payloadBuf ); err != nil {
361
+ if err := cr .readMessage (payloadBuf [:sizeWithTag ]); err != nil {
343
362
if err == io .EOF { // EOF is not expected mid-chunk.
344
363
err = io .ErrUnexpectedEOF
345
364
}
365
+ cr .payload .Release ()
346
366
return nil , err
347
367
}
348
368
return payloadBuf [:size ], nil
@@ -388,6 +408,7 @@ func (c *readConverter) ensureLeftover() error {
388
408
if len (c .leftover ) > 0 {
389
409
return nil
390
410
}
411
+ c .leftover = nil
391
412
payload , err := c .cr .ReadChunk ()
392
413
if err != nil {
393
414
return err
0 commit comments