Bläddra i källkod

Avoid double buffering in TLS hot path

tags/v2.2.2^2^2^2
9seconds 1 månad sedan
förälder
incheckning
cb436efd87
3 ändrade filer med 94 tillägg och 12 borttagningar
  1. 2
    2
      mtglib/internal/doppel/conn.go
  2. 14
    10
      mtglib/internal/tls/utils.go
  3. 78
    0
      mtglib/internal/tls/utils_test.go

+ 2
- 2
mtglib/internal/doppel/conn.go Visa fil

@@ -61,14 +61,14 @@ func (c Conn) start() {
61 61
 		for c.p.writeStream.Len() == 0 && !c.p.done {
62 62
 			c.p.writtenCond.Wait()
63 63
 		}
64
-		n, _ := c.p.writeStream.Read(buf[:size])
64
+		n, _ := c.p.writeStream.Read(buf[tls.SizeHeader : tls.SizeHeader+size])
65 65
 		c.p.writtenCond.L.Unlock()
66 66
 
67 67
 		if n == 0 {
68 68
 			continue
69 69
 		}
70 70
 
71
-		if err := tls.WriteRecord(c.Conn, buf[:n]); err != nil {
71
+		if err := tls.WriteRecordInPlace(c.Conn, buf[:], n); err != nil {
72 72
 			c.p.ctxCancel(err)
73 73
 			return
74 74
 		}

+ 14
- 10
mtglib/internal/tls/utils.go Visa fil

@@ -29,20 +29,24 @@ func ReadRecord(r io.Reader, w io.Writer) (byte, int64, error) {
29 29
 
30 30
 func WriteRecord(w io.Writer, payload []byte) error {
31 31
 	buf := [MaxRecordSize]byte{}
32
-	buf[0] = TypeApplicationData
33
-
34
-	bufV := buf[SizeRecordType:]
35
-	copy(bufV[:SizeVersion], TLSVersion[:])
32
+	copy(buf[SizeHeader:], payload)
36 33
 
37
-	bufS := bufV[SizeVersion:]
38
-	binary.BigEndian.PutUint16(bufS[:SizeSize], uint16(len(payload)))
34
+	return WriteRecordInPlace(w, buf[:], len(payload))
35
+}
39 36
 
40
-	bufP := buf[SizeHeader:]
41
-	if n := copy(bufP, payload); n != len(payload) {
42
-		return fmt.Errorf("copied %d bytes of payload instead of %d", n, len(payload))
37
+func WriteRecordInPlace(w io.Writer, buf []byte, payloadLen int) error {
38
+	if payloadLen > MaxRecordPayloadSize {
39
+		return fmt.Errorf("payload %d exceeds max %d", payloadLen, MaxRecordPayloadSize)
43 40
 	}
44 41
 
45
-	_, err := w.Write(buf[:SizeHeader+len(payload)])
42
+	buf[0] = TypeApplicationData
43
+	copy(buf[SizeRecordType:SizeRecordType+SizeVersion], TLSVersion[:])
44
+	binary.BigEndian.PutUint16(
45
+		buf[SizeRecordType+SizeVersion:SizeRecordType+SizeVersion+SizeSize],
46
+		uint16(payloadLen),
47
+	)
48
+
49
+	_, err := w.Write(buf[:SizeHeader+payloadLen])
46 50
 
47 51
 	return err
48 52
 }

+ 78
- 0
mtglib/internal/tls/utils_test.go Visa fil

@@ -119,6 +119,84 @@ func (suite *UtilsTestSuite) TestWriteRecordPayloadTooLarge() {
119 119
 	suite.Error(err)
120 120
 }
121 121
 
122
+func (suite *UtilsTestSuite) TestWriteRecordInPlace() {
123
+	payload := []byte("hello in-place")
124
+
125
+	var buf [MaxRecordSize]byte
126
+	copy(buf[SizeHeader:], payload)
127
+
128
+	err := WriteRecordInPlace(suite.dst, buf[:], len(payload))
129
+	suite.NoError(err)
130
+
131
+	written := suite.dst.Bytes()
132
+	suite.Equal(byte(TypeApplicationData), written[0])
133
+	suite.Equal(TLSVersion[:], written[SizeRecordType:SizeRecordType+SizeVersion])
134
+
135
+	length := binary.BigEndian.Uint16(written[SizeRecordType+SizeVersion:])
136
+	suite.Equal(uint16(len(payload)), length)
137
+	suite.Equal(payload, written[SizeHeader:])
138
+}
139
+
140
+func (suite *UtilsTestSuite) TestWriteRecordInPlaceRoundTrip() {
141
+	payload := []byte("round trip in-place")
142
+
143
+	var buf [MaxRecordSize]byte
144
+	copy(buf[SizeHeader:], payload)
145
+
146
+	var wire bytes.Buffer
147
+
148
+	err := WriteRecordInPlace(&wire, buf[:], len(payload))
149
+	suite.NoError(err)
150
+
151
+	var recovered bytes.Buffer
152
+
153
+	recordType, length, err := ReadRecord(&wire, &recovered)
154
+	suite.NoError(err)
155
+	suite.Equal(byte(TypeApplicationData), recordType)
156
+	suite.Equal(int64(len(payload)), length)
157
+	suite.Equal(payload, recovered.Bytes())
158
+}
159
+
160
+func (suite *UtilsTestSuite) TestWriteRecordInPlacePayloadTooLarge() {
161
+	var buf [MaxRecordSize]byte
162
+
163
+	err := WriteRecordInPlace(suite.dst, buf[:], MaxRecordPayloadSize+1)
164
+	suite.Error(err)
165
+}
166
+
167
+func (suite *UtilsTestSuite) TestWriteRecordInPlacePropagatesError() {
168
+	m := &WriterMock{}
169
+	m.
170
+		On("Write", mock.AnythingOfType("[]uint8")).
171
+		Once().
172
+		Return(0, errors.New("disk full"))
173
+
174
+	var buf [MaxRecordSize]byte
175
+	copy(buf[SizeHeader:], []byte("data"))
176
+
177
+	err := WriteRecordInPlace(m, buf[:], 4)
178
+	suite.Error(err)
179
+
180
+	m.AssertExpectations(suite.T())
181
+}
182
+
183
+func (suite *UtilsTestSuite) TestWriteRecordInPlaceMatchesWriteRecord() {
184
+	payload := []byte("equivalence check")
185
+
186
+	var legacy bytes.Buffer
187
+	err := WriteRecord(&legacy, payload)
188
+	suite.NoError(err)
189
+
190
+	var buf [MaxRecordSize]byte
191
+	copy(buf[SizeHeader:], payload)
192
+
193
+	var inPlace bytes.Buffer
194
+	err = WriteRecordInPlace(&inPlace, buf[:], len(payload))
195
+	suite.NoError(err)
196
+
197
+	suite.Equal(legacy.Bytes(), inPlace.Bytes())
198
+}
199
+
122 200
 func TestUtils(t *testing.T) {
123 201
 	t.Parallel()
124 202
 	suite.Run(t, &UtilsTestSuite{})

Laddar…
Avbryt
Spara