From 1deb4c7102c02e7d396275f9b6b30dac7554c9dd Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Sat, 30 Nov 2024 14:05:14 +0200 Subject: [PATCH 01/13] ignore pprof files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 2b0c6e4..a80978c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,8 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out +*.pprof + # Dependency directories (remove the comment below to include it) # vendor/ From f3cf7186dc65b0ef31280b3d5c796d4d272b2b6f Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Sat, 30 Nov 2024 14:08:13 +0200 Subject: [PATCH 02/13] add CPU profiling option --- README.md | 2 ++ cmd/main.go | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/README.md b/README.md index a906690..9446276 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ $ basic_hmac_auth -h Usage of /usr/local/bin/basic_hmac_auth: -buffer-size int initial buffer size for stream parsing + -cpu-profile string + write CPU profile to file -secret string hex-encoded HMAC secret value -secret-file string diff --git a/cmd/main.go b/cmd/main.go index f41553d..b57486f 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -9,6 +9,7 @@ import ( "io" "log" "os" + "runtime/pprof" "github.com/SenseUnit/basic_hmac_auth/handler" ) @@ -24,6 +25,7 @@ var ( hexSecret = flag.String("secret", "", "hex-encoded HMAC secret value") hexSecretFile = flag.String("secret-file", "", "file containing single line with hex-encoded secret") showVersion = flag.Bool("version", false, "show program version and exit") + cpuProfile = flag.String("cpu-profile", "", "write CPU profile to file") ) func run() int { @@ -65,6 +67,16 @@ func run() int { return 3 } + if *cpuProfile != "" { + f, err := os.Create(*cpuProfile) + if err != nil { + log.Fatal(err) + } + defer f.Close() + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + } + err = (&handler.BasicHMACAuthHandler{ Secret: secret, BufferSize: *bufferSize, From a3afdd3a0129e21ba141e30b6a5cce58edc399ee Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Sat, 30 Nov 2024 14:12:01 +0200 Subject: [PATCH 03/13] reuse HMAC hasher --- handler/handler.go | 6 +++++- hmac/hmac.go | 10 +++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 2901e05..89f5a57 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -3,6 +3,8 @@ package handler import ( "bufio" "bytes" + chmac "crypto/hmac" + "crypto/sha256" "fmt" "io" @@ -28,6 +30,8 @@ func (a *BasicHMACAuthHandler) Run(input io.Reader, output io.Writer) error { rd := bufio.NewReaderSize(input, bufSize) scanner := proto.NewElasticLineScanner(rd, '\n') + mac := chmac.New(sha256.New, a.Secret) + for scanner.Scan() { parts := bytes.SplitN(scanner.Bytes(), []byte{' '}, 4) if len(parts) < 3 { @@ -38,7 +42,7 @@ func (a *BasicHMACAuthHandler) Run(input io.Reader, output io.Writer) error { username := proto.RFC1738Unescape(parts[1]) password := proto.RFC1738Unescape(parts[2]) - if hmac.VerifyHMACLoginAndPassword(a.Secret, username, password) { + if hmac.VerifyHMACLoginAndPassword(mac, username, password) { fmt.Fprintf(output, "%s OK\n", channelID) } else { fmt.Fprintf(output, "%s ERR\n", channelID) diff --git a/hmac/hmac.go b/hmac/hmac.go index b8dfbbe..4135b6b 100644 --- a/hmac/hmac.go +++ b/hmac/hmac.go @@ -3,9 +3,9 @@ package hmac import ( "bytes" "crypto/hmac" - "crypto/sha256" "encoding/base64" "encoding/binary" + "hash" "time" ) @@ -21,7 +21,7 @@ type HMACToken struct { Signature [HMACSignatureSize]byte } -func VerifyHMACLoginAndPassword(secret, login, password []byte) bool { +func VerifyHMACLoginAndPassword(mac hash.Hash, login, password []byte) bool { rd := base64.NewDecoder(base64.RawURLEncoding, bytes.NewReader(password)) var token HMACToken @@ -33,12 +33,12 @@ func VerifyHMACLoginAndPassword(secret, login, password []byte) bool { return false } - expectedMAC := CalculateHMACSignature(secret, login, token.Expire) + expectedMAC := CalculateHMACSignature(mac, login, token.Expire) return hmac.Equal(token.Signature[:], expectedMAC) } -func CalculateHMACSignature(secret, username []byte, expire int64) []byte { - mac := hmac.New(sha256.New, secret) +func CalculateHMACSignature(mac hash.Hash, username []byte, expire int64) []byte { + mac.Reset() mac.Write(hmacSignaturePrefix) mac.Write(username) binary.Write(mac, binary.BigEndian, expire) From 5970b9e291f2f44eb397a9ff9f41218d8923159c Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Sat, 30 Nov 2024 14:42:59 +0200 Subject: [PATCH 04/13] hmac: get rid of decoder allocations and binary reader reflection --- hmac/hmac.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/hmac/hmac.go b/hmac/hmac.go index 4135b6b..750dff3 100644 --- a/hmac/hmac.go +++ b/hmac/hmac.go @@ -1,12 +1,12 @@ package hmac import ( - "bytes" "crypto/hmac" "encoding/base64" "encoding/binary" "hash" "time" + "unsafe" ) const ( @@ -16,25 +16,26 @@ const ( var hmacSignaturePrefix = []byte(HMACSignaturePrefix) -type HMACToken struct { - Expire int64 - Signature [HMACSignatureSize]byte -} - func VerifyHMACLoginAndPassword(mac hash.Hash, login, password []byte) bool { - rd := base64.NewDecoder(base64.RawURLEncoding, bytes.NewReader(password)) + n, err := base64.RawURLEncoding.Decode(password, password) + if err != nil { + return false + } + password = password[:n] - var token HMACToken - if err := binary.Read(rd, binary.BigEndian, &token); err != nil { + var expire int64 + if len(password) < int(unsafe.Sizeof(expire)) { return false } + expire = int64(binary.BigEndian.Uint64(password[:unsafe.Sizeof(expire)])) + password = password[unsafe.Sizeof(expire):] - if time.Unix(token.Expire, 0).Before(time.Now()) { + if time.Unix(expire, 0).Before(time.Now()) { return false } - expectedMAC := CalculateHMACSignature(mac, login, token.Expire) - return hmac.Equal(token.Signature[:], expectedMAC) + expectedMAC := CalculateHMACSignature(mac, login, expire) + return hmac.Equal(password, expectedMAC) } func CalculateHMACSignature(mac hash.Hash, username []byte, expire int64) []byte { From 50b402308bf7500a753151f338d9b02b9666671c Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Sat, 30 Nov 2024 14:46:16 +0200 Subject: [PATCH 05/13] get rid of unused constants --- hmac/hmac.go | 1 - 1 file changed, 1 deletion(-) diff --git a/hmac/hmac.go b/hmac/hmac.go index 750dff3..a159d5e 100644 --- a/hmac/hmac.go +++ b/hmac/hmac.go @@ -11,7 +11,6 @@ import ( const ( HMACSignaturePrefix = "dumbproxy grant token v1" - HMACSignatureSize = 32 ) var hmacSignaturePrefix = []byte(HMACSignaturePrefix) From e8810ffe1503f01f518bb322b99046d3ca238c00 Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Sat, 30 Nov 2024 15:00:22 +0200 Subject: [PATCH 06/13] hmac: do not reuse input decoding buffer as a scratch buffer --- hmac/hmac.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/hmac/hmac.go b/hmac/hmac.go index a159d5e..277ae8a 100644 --- a/hmac/hmac.go +++ b/hmac/hmac.go @@ -16,25 +16,30 @@ const ( var hmacSignaturePrefix = []byte(HMACSignaturePrefix) func VerifyHMACLoginAndPassword(mac hash.Hash, login, password []byte) bool { - n, err := base64.RawURLEncoding.Decode(password, password) + buf := make([]byte, base64.RawURLEncoding.DecodedLen(len(password))) + n, err := base64.RawURLEncoding.Decode(buf, password) if err != nil { return false } - password = password[:n] + buf = buf[:n] var expire int64 - if len(password) < int(unsafe.Sizeof(expire)) { + if len(buf) < int(unsafe.Sizeof(expire)) { return false } - expire = int64(binary.BigEndian.Uint64(password[:unsafe.Sizeof(expire)])) - password = password[unsafe.Sizeof(expire):] + expire = int64(binary.BigEndian.Uint64(buf[:unsafe.Sizeof(expire)])) + buf = buf[unsafe.Sizeof(expire):] if time.Unix(expire, 0).Before(time.Now()) { return false } + if len(buf) < mac.Size() { + return false + } + expectedMAC := CalculateHMACSignature(mac, login, expire) - return hmac.Equal(password, expectedMAC) + return hmac.Equal(buf[:mac.Size()], expectedMAC) } func CalculateHMACSignature(mac hash.Hash, username []byte, expire int64) []byte { From 7aa10a3553d3e70259d363f8bdee6ad9a30e5314 Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Sat, 30 Nov 2024 15:17:30 +0200 Subject: [PATCH 07/13] hmac: get rid of reflection in hmac calculation as well --- hmac/hmac.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/hmac/hmac.go b/hmac/hmac.go index 277ae8a..6c6de1b 100644 --- a/hmac/hmac.go +++ b/hmac/hmac.go @@ -43,9 +43,13 @@ func VerifyHMACLoginAndPassword(mac hash.Hash, login, password []byte) bool { } func CalculateHMACSignature(mac hash.Hash, username []byte, expire int64) []byte { + var buf [unsafe.Sizeof(expire)]byte + binary.BigEndian.PutUint64(buf[:], uint64(expire)) + mac.Reset() mac.Write(hmacSignaturePrefix) mac.Write(username) - binary.Write(mac, binary.BigEndian, expire) + mac.Write(buf[:]) + return mac.Sum(nil) } From 9a7096c6a5a6db88f76023b2cde31c072feccc26 Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Sat, 30 Nov 2024 15:18:34 +0200 Subject: [PATCH 08/13] hmac: tidy interface --- handler/handler.go | 4 +--- hmac/hmac.go | 5 +++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 89f5a57..9fe6c14 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -3,8 +3,6 @@ package handler import ( "bufio" "bytes" - chmac "crypto/hmac" - "crypto/sha256" "fmt" "io" @@ -30,7 +28,7 @@ func (a *BasicHMACAuthHandler) Run(input io.Reader, output io.Writer) error { rd := bufio.NewReaderSize(input, bufSize) scanner := proto.NewElasticLineScanner(rd, '\n') - mac := chmac.New(sha256.New, a.Secret) + mac := hmac.NewHasher(a.Secret) for scanner.Scan() { parts := bytes.SplitN(scanner.Bytes(), []byte{' '}, 4) diff --git a/hmac/hmac.go b/hmac/hmac.go index 6c6de1b..50d0290 100644 --- a/hmac/hmac.go +++ b/hmac/hmac.go @@ -2,6 +2,7 @@ package hmac import ( "crypto/hmac" + "crypto/sha256" "encoding/base64" "encoding/binary" "hash" @@ -15,6 +16,10 @@ const ( var hmacSignaturePrefix = []byte(HMACSignaturePrefix) +func NewHasher(secret []byte) hash.Hash { + return hmac.New(sha256.New, secret) +} + func VerifyHMACLoginAndPassword(mac hash.Hash, login, password []byte) bool { buf := make([]byte, base64.RawURLEncoding.DecodedLen(len(password))) n, err := base64.RawURLEncoding.Decode(buf, password) From 1309adb5c145c5cc409c263847dfb95c408039b7 Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Sat, 30 Nov 2024 16:12:24 +0200 Subject: [PATCH 09/13] proto: abstract formatting away and reuse formatting buffer --- handler/handler.go | 10 ++++++++-- proto/emit.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 proto/emit.go diff --git a/handler/handler.go b/handler/handler.go index 9fe6c14..9e950b6 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -30,6 +30,8 @@ func (a *BasicHMACAuthHandler) Run(input io.Reader, output io.Writer) error { mac := hmac.NewHasher(a.Secret) + emitter := proto.NewResponseEmitter(output) + for scanner.Scan() { parts := bytes.SplitN(scanner.Bytes(), []byte{' '}, 4) if len(parts) < 3 { @@ -41,9 +43,13 @@ func (a *BasicHMACAuthHandler) Run(input io.Reader, output io.Writer) error { password := proto.RFC1738Unescape(parts[2]) if hmac.VerifyHMACLoginAndPassword(mac, username, password) { - fmt.Fprintf(output, "%s OK\n", channelID) + if err := emitter.EmitOK(channelID); err != nil { + return fmt.Errorf("response write failed: %w", err) + } } else { - fmt.Fprintf(output, "%s ERR\n", channelID) + if err := emitter.EmitERR(channelID); err != nil { + return fmt.Errorf("response write failed: %w", err) + } } } diff --git a/proto/emit.go b/proto/emit.go new file mode 100644 index 0000000..2c2df08 --- /dev/null +++ b/proto/emit.go @@ -0,0 +1,46 @@ +package proto + +import ( + "bytes" + "io" +) + +const ( + OK = "OK" + ERR = "ERR" +) + +type ResponseEmitter struct { + writer io.Writer + buffer bytes.Buffer +} + +func NewResponseEmitter(writer io.Writer) *ResponseEmitter { + return &ResponseEmitter{ + writer: writer, + } +} + +func (e *ResponseEmitter) EmitOK(channelID []byte) error { + e.beginResponse(channelID) + e.buffer.WriteString(OK) + return e.finishResponse() +} + +func (e *ResponseEmitter) EmitERR(channelID []byte) error { + e.beginResponse(channelID) + e.buffer.WriteString(ERR) + return e.finishResponse() +} + +func (e *ResponseEmitter) beginResponse(channelID []byte) { + e.buffer.Reset() + e.buffer.Write(channelID) + e.buffer.WriteByte(' ') +} + +func (e *ResponseEmitter) finishResponse() error { + e.buffer.WriteByte('\n') + _, err := e.buffer.WriteTo(e.writer) + return err +} From 7db735fd246b6da071f8aeac08aaac11a50bf942 Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Sat, 30 Nov 2024 17:51:54 +0200 Subject: [PATCH 10/13] hmac: get rid of unsafe pkg --- hmac/hmac.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hmac/hmac.go b/hmac/hmac.go index 50d0290..ebf1eb7 100644 --- a/hmac/hmac.go +++ b/hmac/hmac.go @@ -7,11 +7,11 @@ import ( "encoding/binary" "hash" "time" - "unsafe" ) const ( HMACSignaturePrefix = "dumbproxy grant token v1" + HMACExpireSize = 8 ) var hmacSignaturePrefix = []byte(HMACSignaturePrefix) @@ -29,11 +29,11 @@ func VerifyHMACLoginAndPassword(mac hash.Hash, login, password []byte) bool { buf = buf[:n] var expire int64 - if len(buf) < int(unsafe.Sizeof(expire)) { + if len(buf) < HMACExpireSize { return false } - expire = int64(binary.BigEndian.Uint64(buf[:unsafe.Sizeof(expire)])) - buf = buf[unsafe.Sizeof(expire):] + expire = int64(binary.BigEndian.Uint64(buf[:HMACExpireSize])) + buf = buf[HMACExpireSize:] if time.Unix(expire, 0).Before(time.Now()) { return false @@ -48,7 +48,7 @@ func VerifyHMACLoginAndPassword(mac hash.Hash, login, password []byte) bool { } func CalculateHMACSignature(mac hash.Hash, username []byte, expire int64) []byte { - var buf [unsafe.Sizeof(expire)]byte + var buf [HMACExpireSize]byte binary.BigEndian.PutUint64(buf[:], uint64(expire)) mac.Reset() From ed8c0d1aa3e446ab4f1720c26558f7d45f139701 Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Sat, 30 Nov 2024 19:28:58 +0200 Subject: [PATCH 11/13] hmac: reuse base64 decoding buffer --- handler/handler.go | 4 ++-- hmac/hmac.go | 41 ++++++++++++++++++++++++++++++----------- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index 9e950b6..bd0d6c7 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -28,7 +28,7 @@ func (a *BasicHMACAuthHandler) Run(input io.Reader, output io.Writer) error { rd := bufio.NewReaderSize(input, bufSize) scanner := proto.NewElasticLineScanner(rd, '\n') - mac := hmac.NewHasher(a.Secret) + verifier := hmac.NewVerifier(a.Secret) emitter := proto.NewResponseEmitter(output) @@ -42,7 +42,7 @@ func (a *BasicHMACAuthHandler) Run(input io.Reader, output io.Writer) error { username := proto.RFC1738Unescape(parts[1]) password := proto.RFC1738Unescape(parts[2]) - if hmac.VerifyHMACLoginAndPassword(mac, username, password) { + if verifier.VerifyLoginAndPassword(username, password) { if err := emitter.EmitOK(channelID); err != nil { return fmt.Errorf("response write failed: %w", err) } diff --git a/hmac/hmac.go b/hmac/hmac.go index ebf1eb7..3e96a8b 100644 --- a/hmac/hmac.go +++ b/hmac/hmac.go @@ -12,6 +12,7 @@ import ( const ( HMACSignaturePrefix = "dumbproxy grant token v1" HMACExpireSize = 8 + passwordBufferSize = HMACExpireSize + 64 // for worst case if 512-bit hash is used for some reason ) var hmacSignaturePrefix = []byte(HMACSignaturePrefix) @@ -20,8 +21,26 @@ func NewHasher(secret []byte) hash.Hash { return hmac.New(sha256.New, secret) } -func VerifyHMACLoginAndPassword(mac hash.Hash, login, password []byte) bool { - buf := make([]byte, base64.RawURLEncoding.DecodedLen(len(password))) +type Verifier struct { + mac hash.Hash + buf []byte +} + +func NewVerifier(secret []byte) *Verifier { + return &Verifier{ + mac: hmac.New(sha256.New, secret), + } +} + +func (v *Verifier) ensureBufferSize(size int) { + if len(v.buf) < size { + v.buf = make([]byte, size) + } +} + +func (v *Verifier) VerifyLoginAndPassword(login, password []byte) bool { + v.ensureBufferSize(base64.RawURLEncoding.DecodedLen(len(password))) + buf := v.buf n, err := base64.RawURLEncoding.Decode(buf, password) if err != nil { return false @@ -39,22 +58,22 @@ func VerifyHMACLoginAndPassword(mac hash.Hash, login, password []byte) bool { return false } - if len(buf) < mac.Size() { + if len(buf) < v.mac.Size() { return false } - expectedMAC := CalculateHMACSignature(mac, login, expire) - return hmac.Equal(buf[:mac.Size()], expectedMAC) + expectedMAC := v.calculateHMACSignature(login, expire) + return hmac.Equal(buf[:v.mac.Size()], expectedMAC) } -func CalculateHMACSignature(mac hash.Hash, username []byte, expire int64) []byte { +func (v *Verifier) calculateHMACSignature(username []byte, expire int64) []byte { var buf [HMACExpireSize]byte binary.BigEndian.PutUint64(buf[:], uint64(expire)) - mac.Reset() - mac.Write(hmacSignaturePrefix) - mac.Write(username) - mac.Write(buf[:]) + v.mac.Reset() + v.mac.Write(hmacSignaturePrefix) + v.mac.Write(username) + v.mac.Write(buf[:]) - return mac.Sum(nil) + return v.mac.Sum(nil) } From a1c3e8a6a6a4b9415e59c28bbed505520eaf0531 Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Sat, 30 Nov 2024 20:07:18 +0200 Subject: [PATCH 12/13] handler: reduce allocations --- handler/handler.go | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/handler/handler.go b/handler/handler.go index bd0d6c7..d24ab91 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -33,14 +33,22 @@ func (a *BasicHMACAuthHandler) Run(input io.Reader, output io.Writer) error { emitter := proto.NewResponseEmitter(output) for scanner.Scan() { - parts := bytes.SplitN(scanner.Bytes(), []byte{' '}, 4) - if len(parts) < 3 { - err := fmt.Errorf("bad request line sent to auth helper: %q", string(scanner.Bytes())) - return err + line := scanner.Bytes() + + before, after, found := bytes.Cut(line, []byte{' '}) + if !found { + return fmt.Errorf("bad request line sent to auth helper: %q", line) + } + channelID := before + + before, after, found = bytes.Cut(after, []byte{' '}) + if !found { + return fmt.Errorf("bad request line sent to auth helper: %q", line) } - channelID := parts[0] - username := proto.RFC1738Unescape(parts[1]) - password := proto.RFC1738Unescape(parts[2]) + username := proto.RFC1738Unescape(before) + + before, _, _ = bytes.Cut(after, []byte{' '}) + password := proto.RFC1738Unescape(before) if verifier.VerifyLoginAndPassword(username, password) { if err := emitter.EmitOK(channelID); err != nil { From 7bb7017a09ba31ee45ad70e2a510bc502b140b42 Mon Sep 17 00:00:00 2001 From: Vladislav Yarmak Date: Sat, 30 Nov 2024 20:45:47 +0200 Subject: [PATCH 13/13] proto: adaptive reusable buffer for scanner --- proto/scanner.go | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/proto/scanner.go b/proto/scanner.go index 1545c78..e97e994 100644 --- a/proto/scanner.go +++ b/proto/scanner.go @@ -1,20 +1,23 @@ package proto -import "io" +import ( + "bufio" + "io" +) -type BytesReader interface { - ReadBytes(byte) ([]byte, error) +type ReadSlicer interface { + ReadSlice(byte) ([]byte, error) } type ElasticLineScanner struct { line []byte - reader BytesReader + reader ReadSlicer lastErr error done bool delim byte } -func NewElasticLineScanner(reader BytesReader, delim byte) *ElasticLineScanner { +func NewElasticLineScanner(reader ReadSlicer, delim byte) *ElasticLineScanner { return &ElasticLineScanner{ reader: reader, delim: delim, @@ -37,19 +40,28 @@ func (els *ElasticLineScanner) Scan() bool { return false } - data, err := els.reader.ReadBytes(els.delim) + els.line = els.line[:0] + var ( + data []byte + err error + ) + for data, err = els.reader.ReadSlice(els.delim); ; data, err = els.reader.ReadSlice(els.delim) { + els.line = append(els.line, data...) + if err != bufio.ErrBufferFull { + break + } + } if err != nil { els.done = true els.lastErr = err - if len(data) == 0 { + if len(els.line) == 0 { return false } } else { // strip delimiter if needed - if len(data) > 0 && data[len(data)-1] == els.delim { - data = data[:len(data)-1] + if len(els.line) > 0 && els.line[len(els.line)-1] == els.delim { + els.line = els.line[:len(els.line)-1] } } - els.line = data return true }