Skip to content

Commit 8a37a56

Browse files
authored
Merge pull request #1 from SenseUnit/perf
Performance improvements
2 parents f63e0ec + 7bb7017 commit 8a37a56

File tree

7 files changed

+161
-38
lines changed

7 files changed

+161
-38
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
# Output of the go coverage tool, specifically when used with LiteIDE
1515
*.out
1616

17+
*.pprof
18+
1719
# Dependency directories (remove the comment below to include it)
1820
# vendor/
1921

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ $ basic_hmac_auth -h
3939
Usage of /usr/local/bin/basic_hmac_auth:
4040
-buffer-size int
4141
initial buffer size for stream parsing
42+
-cpu-profile string
43+
write CPU profile to file
4244
-secret string
4345
hex-encoded HMAC secret value
4446
-secret-file string

cmd/main.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"io"
1010
"log"
1111
"os"
12+
"runtime/pprof"
1213

1314
"github.com/SenseUnit/basic_hmac_auth/handler"
1415
)
@@ -24,6 +25,7 @@ var (
2425
hexSecret = flag.String("secret", "", "hex-encoded HMAC secret value")
2526
hexSecretFile = flag.String("secret-file", "", "file containing single line with hex-encoded secret")
2627
showVersion = flag.Bool("version", false, "show program version and exit")
28+
cpuProfile = flag.String("cpu-profile", "", "write CPU profile to file")
2729
)
2830

2931
func run() int {
@@ -65,6 +67,16 @@ func run() int {
6567
return 3
6668
}
6769

70+
if *cpuProfile != "" {
71+
f, err := os.Create(*cpuProfile)
72+
if err != nil {
73+
log.Fatal(err)
74+
}
75+
defer f.Close()
76+
pprof.StartCPUProfile(f)
77+
defer pprof.StopCPUProfile()
78+
}
79+
6880
err = (&handler.BasicHMACAuthHandler{
6981
Secret: secret,
7082
BufferSize: *bufferSize,

handler/handler.go

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,36 @@ func (a *BasicHMACAuthHandler) Run(input io.Reader, output io.Writer) error {
2828
rd := bufio.NewReaderSize(input, bufSize)
2929
scanner := proto.NewElasticLineScanner(rd, '\n')
3030

31+
verifier := hmac.NewVerifier(a.Secret)
32+
33+
emitter := proto.NewResponseEmitter(output)
34+
3135
for scanner.Scan() {
32-
parts := bytes.SplitN(scanner.Bytes(), []byte{' '}, 4)
33-
if len(parts) < 3 {
34-
err := fmt.Errorf("bad request line sent to auth helper: %q", string(scanner.Bytes()))
35-
return err
36+
line := scanner.Bytes()
37+
38+
before, after, found := bytes.Cut(line, []byte{' '})
39+
if !found {
40+
return fmt.Errorf("bad request line sent to auth helper: %q", line)
3641
}
37-
channelID := parts[0]
38-
username := proto.RFC1738Unescape(parts[1])
39-
password := proto.RFC1738Unescape(parts[2])
42+
channelID := before
43+
44+
before, after, found = bytes.Cut(after, []byte{' '})
45+
if !found {
46+
return fmt.Errorf("bad request line sent to auth helper: %q", line)
47+
}
48+
username := proto.RFC1738Unescape(before)
49+
50+
before, _, _ = bytes.Cut(after, []byte{' '})
51+
password := proto.RFC1738Unescape(before)
4052

41-
if hmac.VerifyHMACLoginAndPassword(a.Secret, username, password) {
42-
fmt.Fprintf(output, "%s OK\n", channelID)
53+
if verifier.VerifyLoginAndPassword(username, password) {
54+
if err := emitter.EmitOK(channelID); err != nil {
55+
return fmt.Errorf("response write failed: %w", err)
56+
}
4357
} else {
44-
fmt.Fprintf(output, "%s ERR\n", channelID)
58+
if err := emitter.EmitERR(channelID); err != nil {
59+
return fmt.Errorf("response write failed: %w", err)
60+
}
4561
}
4662
}
4763

hmac/hmac.go

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,79 @@
11
package hmac
22

33
import (
4-
"bytes"
54
"crypto/hmac"
65
"crypto/sha256"
76
"encoding/base64"
87
"encoding/binary"
8+
"hash"
99
"time"
1010
)
1111

1212
const (
1313
HMACSignaturePrefix = "dumbproxy grant token v1"
14-
HMACSignatureSize = 32
14+
HMACExpireSize = 8
15+
passwordBufferSize = HMACExpireSize + 64 // for worst case if 512-bit hash is used for some reason
1516
)
1617

1718
var hmacSignaturePrefix = []byte(HMACSignaturePrefix)
1819

19-
type HMACToken struct {
20-
Expire int64
21-
Signature [HMACSignatureSize]byte
20+
func NewHasher(secret []byte) hash.Hash {
21+
return hmac.New(sha256.New, secret)
2222
}
2323

24-
func VerifyHMACLoginAndPassword(secret, login, password []byte) bool {
25-
rd := base64.NewDecoder(base64.RawURLEncoding, bytes.NewReader(password))
24+
type Verifier struct {
25+
mac hash.Hash
26+
buf []byte
27+
}
28+
29+
func NewVerifier(secret []byte) *Verifier {
30+
return &Verifier{
31+
mac: hmac.New(sha256.New, secret),
32+
}
33+
}
2634

27-
var token HMACToken
28-
if err := binary.Read(rd, binary.BigEndian, &token); err != nil {
35+
func (v *Verifier) ensureBufferSize(size int) {
36+
if len(v.buf) < size {
37+
v.buf = make([]byte, size)
38+
}
39+
}
40+
41+
func (v *Verifier) VerifyLoginAndPassword(login, password []byte) bool {
42+
v.ensureBufferSize(base64.RawURLEncoding.DecodedLen(len(password)))
43+
buf := v.buf
44+
n, err := base64.RawURLEncoding.Decode(buf, password)
45+
if err != nil {
2946
return false
3047
}
48+
buf = buf[:n]
3149

32-
if time.Unix(token.Expire, 0).Before(time.Now()) {
50+
var expire int64
51+
if len(buf) < HMACExpireSize {
3352
return false
3453
}
54+
expire = int64(binary.BigEndian.Uint64(buf[:HMACExpireSize]))
55+
buf = buf[HMACExpireSize:]
3556

36-
expectedMAC := CalculateHMACSignature(secret, login, token.Expire)
37-
return hmac.Equal(token.Signature[:], expectedMAC)
57+
if time.Unix(expire, 0).Before(time.Now()) {
58+
return false
59+
}
60+
61+
if len(buf) < v.mac.Size() {
62+
return false
63+
}
64+
65+
expectedMAC := v.calculateHMACSignature(login, expire)
66+
return hmac.Equal(buf[:v.mac.Size()], expectedMAC)
3867
}
3968

40-
func CalculateHMACSignature(secret, username []byte, expire int64) []byte {
41-
mac := hmac.New(sha256.New, secret)
42-
mac.Write(hmacSignaturePrefix)
43-
mac.Write(username)
44-
binary.Write(mac, binary.BigEndian, expire)
45-
return mac.Sum(nil)
69+
func (v *Verifier) calculateHMACSignature(username []byte, expire int64) []byte {
70+
var buf [HMACExpireSize]byte
71+
binary.BigEndian.PutUint64(buf[:], uint64(expire))
72+
73+
v.mac.Reset()
74+
v.mac.Write(hmacSignaturePrefix)
75+
v.mac.Write(username)
76+
v.mac.Write(buf[:])
77+
78+
return v.mac.Sum(nil)
4679
}

proto/emit.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package proto
2+
3+
import (
4+
"bytes"
5+
"io"
6+
)
7+
8+
const (
9+
OK = "OK"
10+
ERR = "ERR"
11+
)
12+
13+
type ResponseEmitter struct {
14+
writer io.Writer
15+
buffer bytes.Buffer
16+
}
17+
18+
func NewResponseEmitter(writer io.Writer) *ResponseEmitter {
19+
return &ResponseEmitter{
20+
writer: writer,
21+
}
22+
}
23+
24+
func (e *ResponseEmitter) EmitOK(channelID []byte) error {
25+
e.beginResponse(channelID)
26+
e.buffer.WriteString(OK)
27+
return e.finishResponse()
28+
}
29+
30+
func (e *ResponseEmitter) EmitERR(channelID []byte) error {
31+
e.beginResponse(channelID)
32+
e.buffer.WriteString(ERR)
33+
return e.finishResponse()
34+
}
35+
36+
func (e *ResponseEmitter) beginResponse(channelID []byte) {
37+
e.buffer.Reset()
38+
e.buffer.Write(channelID)
39+
e.buffer.WriteByte(' ')
40+
}
41+
42+
func (e *ResponseEmitter) finishResponse() error {
43+
e.buffer.WriteByte('\n')
44+
_, err := e.buffer.WriteTo(e.writer)
45+
return err
46+
}

proto/scanner.go

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
package proto
22

3-
import "io"
3+
import (
4+
"bufio"
5+
"io"
6+
)
47

5-
type BytesReader interface {
6-
ReadBytes(byte) ([]byte, error)
8+
type ReadSlicer interface {
9+
ReadSlice(byte) ([]byte, error)
710
}
811

912
type ElasticLineScanner struct {
1013
line []byte
11-
reader BytesReader
14+
reader ReadSlicer
1215
lastErr error
1316
done bool
1417
delim byte
1518
}
1619

17-
func NewElasticLineScanner(reader BytesReader, delim byte) *ElasticLineScanner {
20+
func NewElasticLineScanner(reader ReadSlicer, delim byte) *ElasticLineScanner {
1821
return &ElasticLineScanner{
1922
reader: reader,
2023
delim: delim,
@@ -37,19 +40,28 @@ func (els *ElasticLineScanner) Scan() bool {
3740
return false
3841
}
3942

40-
data, err := els.reader.ReadBytes(els.delim)
43+
els.line = els.line[:0]
44+
var (
45+
data []byte
46+
err error
47+
)
48+
for data, err = els.reader.ReadSlice(els.delim); ; data, err = els.reader.ReadSlice(els.delim) {
49+
els.line = append(els.line, data...)
50+
if err != bufio.ErrBufferFull {
51+
break
52+
}
53+
}
4154
if err != nil {
4255
els.done = true
4356
els.lastErr = err
44-
if len(data) == 0 {
57+
if len(els.line) == 0 {
4558
return false
4659
}
4760
} else {
4861
// strip delimiter if needed
49-
if len(data) > 0 && data[len(data)-1] == els.delim {
50-
data = data[:len(data)-1]
62+
if len(els.line) > 0 && els.line[len(els.line)-1] == els.delim {
63+
els.line = els.line[:len(els.line)-1]
5164
}
5265
}
53-
els.line = data
5466
return true
5567
}

0 commit comments

Comments
 (0)