Browse Source

use the new goshuirc ircreader

tags/v2.6.0-rc1
Shivaram Lingamneni 3 years ago
parent
commit
d0e11f49ad
7 changed files with 141 additions and 199 deletions
  1. 1
    1
      go.mod
  2. 4
    0
      go.sum
  3. 3
    1
      irc/client.go
  4. 14
    61
      irc/ircconn.go
  5. 0
    135
      irc/ircconn_test.go
  6. 117
    0
      vendor/github.com/goshuirc/irc-go/ircreader/ircreader.go
  7. 2
    1
      vendor/modules.txt

+ 1
- 1
go.mod View File

@@ -10,7 +10,7 @@ require (
10 10
 	github.com/go-sql-driver/mysql v1.5.0
11 11
 	github.com/go-test/deep v1.0.6 // indirect
12 12
 	github.com/gorilla/websocket v1.4.2
13
-	github.com/goshuirc/irc-go v0.0.0-20210108124156-ec778d0252a5
13
+	github.com/goshuirc/irc-go v0.0.0-20210214015142-9d703e6ac38a
14 14
 	github.com/onsi/ginkgo v1.12.0 // indirect
15 15
 	github.com/onsi/gomega v1.9.0 // indirect
16 16
 	github.com/oragono/confusables v0.0.0-20201108231250-4ab98ab61fb1

+ 4
- 0
go.sum View File

@@ -28,6 +28,10 @@ github.com/goshuirc/irc-go v0.0.0-20201228002532-4e36cb3f41f1 h1:Kyyey3K8nhx60lt
28 28
 github.com/goshuirc/irc-go v0.0.0-20201228002532-4e36cb3f41f1/go.mod h1:q/JhvvKLmif3y9q8MDQM+gRCnjEKnu5ClF298TTXJug=
29 29
 github.com/goshuirc/irc-go v0.0.0-20210108124156-ec778d0252a5 h1:TXGvyYHJEBluqwI8d0V5/QmSnNxEYIMbfPE36B8CNK8=
30 30
 github.com/goshuirc/irc-go v0.0.0-20210108124156-ec778d0252a5/go.mod h1:q/JhvvKLmif3y9q8MDQM+gRCnjEKnu5ClF298TTXJug=
31
+github.com/goshuirc/irc-go v0.0.0-20210214005848-fcaabd19f360 h1:ChbmWPZwyfgsZd6zxw7B/4hWJE7ezmb69PezUM9+YA4=
32
+github.com/goshuirc/irc-go v0.0.0-20210214005848-fcaabd19f360/go.mod h1:q/JhvvKLmif3y9q8MDQM+gRCnjEKnu5ClF298TTXJug=
33
+github.com/goshuirc/irc-go v0.0.0-20210214015142-9d703e6ac38a h1:PR1tw21nn93AwKmjEPA7IVHiT+ld9qgO1H32APCMvL0=
34
+github.com/goshuirc/irc-go v0.0.0-20210214015142-9d703e6ac38a/go.mod h1:q/JhvvKLmif3y9q8MDQM+gRCnjEKnu5ClF298TTXJug=
31 35
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
32 36
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
33 37
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=

+ 3
- 1
irc/client.go View File

@@ -18,7 +18,9 @@ import (
18 18
 
19 19
 	"github.com/goshuirc/irc-go/ircfmt"
20 20
 	"github.com/goshuirc/irc-go/ircmsg"
21
+	"github.com/goshuirc/irc-go/ircreader"
21 22
 	ident "github.com/oragono/go-ident"
23
+
22 24
 	"github.com/oragono/oragono/irc/caps"
23 25
 	"github.com/oragono/oragono/irc/connection_limits"
24 26
 	"github.com/oragono/oragono/irc/flatip"
@@ -689,7 +691,7 @@ func (client *Client) run(session *Session) {
689 691
 		} else if err != nil {
690 692
 			var quitMessage string
691 693
 			switch err {
692
-			case errReadQ, errWSBinaryMessage:
694
+			case ircreader.ErrReadQ, errWSBinaryMessage:
693 695
 				quitMessage = err.Error()
694 696
 			default:
695 697
 				quitMessage = "connection closed"

+ 14
- 61
irc/ircconn.go View File

@@ -6,12 +6,12 @@ package irc
6 6
 import (
7 7
 	"bytes"
8 8
 	"errors"
9
-	"io"
10 9
 	"net"
11 10
 	"unicode/utf8"
12 11
 
13 12
 	"github.com/gorilla/websocket"
14 13
 	"github.com/goshuirc/irc-go/ircmsg"
14
+	"github.com/goshuirc/irc-go/ircreader"
15 15
 
16 16
 	"github.com/oragono/oragono/irc/utils"
17 17
 )
@@ -23,7 +23,6 @@ const (
23 23
 
24 24
 var (
25 25
 	crlf               = []byte{'\r', '\n'}
26
-	errReadQ           = errors.New("ReadQ Exceeded")
27 26
 	errWSBinaryMessage = errors.New("WebSocket binary messages are unsupported")
28 27
 )
29 28
 
@@ -48,17 +47,14 @@ type IRCConn interface {
48 47
 type IRCStreamConn struct {
49 48
 	conn *utils.WrappedConn
50 49
 
51
-	buf        []byte
52
-	start      int // start of valid (i.e., read but not yet consumed) data in the buffer
53
-	end        int // end of valid data in the buffer
54
-	searchFrom int // start of valid data in the buffer not yet searched for \n
55
-	eof        bool
50
+	reader ircreader.IRCReader
56 51
 }
57 52
 
58 53
 func NewIRCStreamConn(conn *utils.WrappedConn) *IRCStreamConn {
59
-	return &IRCStreamConn{
60
-		conn: conn,
61
-	}
54
+	var c IRCStreamConn
55
+	c.conn = conn
56
+	c.reader.Initialize(conn.Conn, initialBufferSize, maxReadQBytes)
57
+	return &c
62 58
 }
63 59
 
64 60
 func (cc *IRCStreamConn) UnderlyingConn() *utils.WrappedConn {
@@ -78,56 +74,13 @@ func (cc *IRCStreamConn) WriteLines(buffers [][]byte) (err error) {
78 74
 }
79 75
 
80 76
 func (cc *IRCStreamConn) ReadLine() ([]byte, error) {
81
-	for {
82
-		// try to find a terminated line in the buffered data already read
83
-		nlidx := bytes.IndexByte(cc.buf[cc.searchFrom:cc.end], '\n')
84
-		if nlidx != -1 {
85
-			// got a complete line
86
-			line := cc.buf[cc.start : cc.searchFrom+nlidx]
87
-			cc.start = cc.searchFrom + nlidx + 1
88
-			cc.searchFrom = cc.start
89
-			if globalUtf8EnforcementSetting && !utf8.Valid(line) {
90
-				return line, errInvalidUtf8
91
-			} else {
92
-				return line, nil
93
-			}
94
-		}
95
-
96
-		if cc.start == 0 && len(cc.buf) == maxReadQBytes {
97
-			return nil, errReadQ // out of space, can't expand or slide
98
-		}
99
-
100
-		if cc.eof {
101
-			return nil, io.EOF
102
-		}
103
-
104
-		if len(cc.buf) < maxReadQBytes && (len(cc.buf)-(cc.end-cc.start) < initialBufferSize/2) {
105
-			// allocate a new buffer, copy any remaining data
106
-			newLen := utils.RoundUpToPowerOfTwo(len(cc.buf) + 1)
107
-			if newLen > maxReadQBytes {
108
-				newLen = maxReadQBytes
109
-			} else if newLen < initialBufferSize {
110
-				newLen = initialBufferSize
111
-			}
112
-			newBuf := make([]byte, newLen)
113
-			copy(newBuf, cc.buf[cc.start:cc.end])
114
-			cc.buf = newBuf
115
-		} else if cc.start != 0 {
116
-			// slide remaining data back to the front of the buffer
117
-			copy(cc.buf, cc.buf[cc.start:cc.end])
118
-		}
119
-		cc.end = cc.end - cc.start
120
-		cc.start = 0
121
-
122
-		cc.searchFrom = cc.end
123
-		n, err := cc.conn.Read(cc.buf[cc.end:])
124
-		cc.end += n
125
-		if n != 0 && err == io.EOF {
126
-			// we may have received new \n-terminated lines, try to parse them
127
-			cc.eof = true
128
-		} else if err != nil {
129
-			return nil, err
130
-		}
77
+	line, err := cc.reader.ReadLine()
78
+	if err != nil {
79
+		return nil, err
80
+	} else if globalUtf8EnforcementSetting && !utf8.Valid(line) {
81
+		return line, errInvalidUtf8
82
+	} else {
83
+		return line, nil
131 84
 	}
132 85
 }
133 86
 
@@ -175,7 +128,7 @@ func (wc IRCWSConn) ReadLine() (line []byte, err error) {
175 128
 			return nil, errWSBinaryMessage
176 129
 		}
177 130
 	} else if err == websocket.ErrReadLimit {
178
-		return line, errReadQ
131
+		return line, ircreader.ErrReadQ
179 132
 	} else {
180 133
 		return line, err
181 134
 	}

+ 0
- 135
irc/ircconn_test.go View File

@@ -1,135 +0,0 @@
1
-// Copyright (c) 2020 Shivaram Lingamneni <slingamn@cs.stanford.edu>
2
-// released under the MIT license
3
-
4
-package irc
5
-
6
-import (
7
-	"io"
8
-	"math/rand"
9
-	"net"
10
-	"reflect"
11
-	"testing"
12
-	"time"
13
-
14
-	"github.com/oragono/oragono/irc/utils"
15
-)
16
-
17
-// mockConn is a fake net.Conn / io.Reader that yields len(counts) lines,
18
-// each consisting of counts[i] 'a' characters and a terminating '\n'
19
-type mockConn struct {
20
-	counts []int
21
-}
22
-
23
-func min(i, j int) (m int) {
24
-	if i < j {
25
-		return i
26
-	} else {
27
-		return j
28
-	}
29
-}
30
-
31
-func (c *mockConn) Read(b []byte) (n int, err error) {
32
-	for len(b) > 0 {
33
-		if len(c.counts) == 0 {
34
-			return n, io.EOF
35
-		}
36
-		if c.counts[0] == 0 {
37
-			b[0] = '\n'
38
-			c.counts = c.counts[1:]
39
-			b = b[1:]
40
-			n += 1
41
-			continue
42
-		}
43
-		size := min(c.counts[0], len(b))
44
-		for i := 0; i < size; i++ {
45
-			b[i] = 'a'
46
-		}
47
-		c.counts[0] -= size
48
-		b = b[size:]
49
-		n += size
50
-	}
51
-	return n, nil
52
-}
53
-
54
-func (c *mockConn) Write(b []byte) (n int, err error) {
55
-	return
56
-}
57
-
58
-func (c *mockConn) Close() error {
59
-	c.counts = nil
60
-	return nil
61
-}
62
-
63
-func (c *mockConn) LocalAddr() net.Addr {
64
-	return nil
65
-}
66
-
67
-func (c *mockConn) RemoteAddr() net.Addr {
68
-	return nil
69
-}
70
-
71
-func (c *mockConn) SetDeadline(t time.Time) error {
72
-	return nil
73
-}
74
-
75
-func (c *mockConn) SetReadDeadline(t time.Time) error {
76
-	return nil
77
-}
78
-
79
-func (c *mockConn) SetWriteDeadline(t time.Time) error {
80
-	return nil
81
-}
82
-
83
-func newMockConn(counts []int) *utils.WrappedConn {
84
-	cpCounts := make([]int, len(counts))
85
-	copy(cpCounts, counts)
86
-	c := &mockConn{
87
-		counts: cpCounts,
88
-	}
89
-	return &utils.WrappedConn{
90
-		Conn: c,
91
-	}
92
-}
93
-
94
-// construct a mock reader with some number of \n-terminated lines,
95
-// verify that IRCStreamConn can read and split them as expected
96
-func doLineReaderTest(counts []int, t *testing.T) {
97
-	c := newMockConn(counts)
98
-	r := NewIRCStreamConn(c)
99
-	var readCounts []int
100
-	for {
101
-		line, err := r.ReadLine()
102
-		if err == nil {
103
-			readCounts = append(readCounts, len(line))
104
-		} else if err == io.EOF {
105
-			break
106
-		} else {
107
-			panic(err)
108
-		}
109
-	}
110
-
111
-	if !reflect.DeepEqual(counts, readCounts) {
112
-		t.Errorf("expected %#v, got %#v", counts, readCounts)
113
-	}
114
-}
115
-
116
-const (
117
-	maxMockReaderLen     = 100
118
-	maxMockReaderLineLen = 4096 + 511
119
-)
120
-
121
-func TestLineReader(t *testing.T) {
122
-	counts := []int{44, 428, 3, 0, 200, 2000, 0, 4044, 33, 3, 2, 1, 0, 1, 2, 3, 48, 555}
123
-	doLineReaderTest(counts, t)
124
-
125
-	// fuzz
126
-	r := rand.New(rand.NewSource(time.Now().UnixNano()))
127
-	for i := 0; i < 1000; i++ {
128
-		countsLen := r.Intn(maxMockReaderLen) + 1
129
-		counts := make([]int, countsLen)
130
-		for i := 0; i < countsLen; i++ {
131
-			counts[i] = r.Intn(maxMockReaderLineLen)
132
-		}
133
-		doLineReaderTest(counts, t)
134
-	}
135
-}

+ 117
- 0
vendor/github.com/goshuirc/irc-go/ircreader/ircreader.go View File

@@ -0,0 +1,117 @@
1
+// Copyright (c) 2020-2021 Shivaram Lingamneni
2
+// released under the MIT license
3
+
4
+package ircreader
5
+
6
+import (
7
+	"bytes"
8
+	"errors"
9
+	"io"
10
+)
11
+
12
+/*
13
+IRCReader is an optimized line reader for IRC lines containing tags;
14
+most IRC lines will not approach the maximum line length (8191 bytes
15
+of tag data, plus 512 bytes of message data), so we want a buffered
16
+reader that can start with a smaller buffer and expand if necessary,
17
+while also maintaining a hard upper limit on the size of the buffer.
18
+*/
19
+
20
+var (
21
+	ErrReadQ = errors.New("readQ exceeded (read too many bytes without terminating newline)")
22
+)
23
+
24
+type IRCReader struct {
25
+	conn io.Reader
26
+
27
+	initialSize int
28
+	maxSize     int
29
+
30
+	buf        []byte
31
+	start      int // start of valid (i.e., read but not yet consumed) data in the buffer
32
+	end        int // end of valid data in the buffer
33
+	searchFrom int // start of valid data in the buffer not yet searched for \n
34
+	eof        bool
35
+}
36
+
37
+// Returns a new *IRCReader with sane buffer size limits.
38
+func NewIRCReader(conn io.Reader) *IRCReader {
39
+	var reader IRCReader
40
+	reader.Initialize(conn, 512, 8192+1024)
41
+	return &reader
42
+}
43
+
44
+// "Placement new" for an IRCReader; initializes it with custom buffer size
45
+// limits.
46
+func (cc *IRCReader) Initialize(conn io.Reader, initialSize, maxSize int) {
47
+	*cc = IRCReader{}
48
+	cc.conn = conn
49
+	cc.initialSize = initialSize
50
+	cc.maxSize = maxSize
51
+}
52
+
53
+// Blocks until a full IRC line is read, then returns it. Accepts either \n
54
+// or \r\n as the line terminator (but not \r in isolation). Passes through
55
+// errors from the underlying connection. Returns ErrReadQ if the buffer limit
56
+// was exceeded without a terminating \n.
57
+func (cc *IRCReader) ReadLine() ([]byte, error) {
58
+	for {
59
+		// try to find a terminated line in the buffered data already read
60
+		nlidx := bytes.IndexByte(cc.buf[cc.searchFrom:cc.end], '\n')
61
+		if nlidx != -1 {
62
+			// got a complete line
63
+			line := cc.buf[cc.start : cc.searchFrom+nlidx]
64
+			cc.start = cc.searchFrom + nlidx + 1
65
+			cc.searchFrom = cc.start
66
+			return line, nil
67
+		}
68
+
69
+		if cc.start == 0 && len(cc.buf) == cc.maxSize {
70
+			return nil, ErrReadQ // out of space, can't expand or slide
71
+		}
72
+
73
+		if cc.eof {
74
+			return nil, io.EOF
75
+		}
76
+
77
+		if len(cc.buf) < cc.maxSize && (len(cc.buf)-(cc.end-cc.start) < cc.initialSize/2) {
78
+			// allocate a new buffer, copy any remaining data
79
+			newLen := roundUpToPowerOfTwo(len(cc.buf) + 1)
80
+			if newLen > cc.maxSize {
81
+				newLen = cc.maxSize
82
+			} else if newLen < cc.initialSize {
83
+				newLen = cc.initialSize
84
+			}
85
+			newBuf := make([]byte, newLen)
86
+			copy(newBuf, cc.buf[cc.start:cc.end])
87
+			cc.buf = newBuf
88
+		} else if cc.start != 0 {
89
+			// slide remaining data back to the front of the buffer
90
+			copy(cc.buf, cc.buf[cc.start:cc.end])
91
+		}
92
+		cc.end = cc.end - cc.start
93
+		cc.start = 0
94
+
95
+		cc.searchFrom = cc.end
96
+		n, err := cc.conn.Read(cc.buf[cc.end:])
97
+		cc.end += n
98
+		if n != 0 && err == io.EOF {
99
+			// we may have received new \n-terminated lines, try to parse them
100
+			cc.eof = true
101
+		} else if err != nil {
102
+			return nil, err
103
+		}
104
+	}
105
+}
106
+
107
+// return n such that v <= n and n == 2**i for some i
108
+func roundUpToPowerOfTwo(v int) int {
109
+	// http://graphics.stanford.edu/~seander/bithacks.html
110
+	v -= 1
111
+	v |= v >> 1
112
+	v |= v >> 2
113
+	v |= v >> 4
114
+	v |= v >> 8
115
+	v |= v >> 16
116
+	return v + 1
117
+}

+ 2
- 1
vendor/modules.txt View File

@@ -21,10 +21,11 @@ github.com/go-sql-driver/mysql
21 21
 # github.com/gorilla/websocket v1.4.2
22 22
 ## explicit
23 23
 github.com/gorilla/websocket
24
-# github.com/goshuirc/irc-go v0.0.0-20210108124156-ec778d0252a5
24
+# github.com/goshuirc/irc-go v0.0.0-20210214015142-9d703e6ac38a
25 25
 ## explicit
26 26
 github.com/goshuirc/irc-go/ircfmt
27 27
 github.com/goshuirc/irc-go/ircmsg
28
+github.com/goshuirc/irc-go/ircreader
28 29
 # github.com/onsi/ginkgo v1.12.0
29 30
 ## explicit
30 31
 # github.com/onsi/gomega v1.9.0

Loading…
Cancel
Save