-
-
Notifications
You must be signed in to change notification settings - Fork 85
Expand file tree
/
Copy pathcapsule.go
More file actions
261 lines (232 loc) · 7.17 KB
/
Copy pathcapsule.go
File metadata and controls
261 lines (232 loc) · 7.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
package webtransport
import (
"encoding/binary"
"errors"
"io"
"unicode/utf8"
"github.com/quic-go/quic-go/http3"
"github.com/quic-go/quic-go/quicvarint"
)
const (
closeSessionCapsuleType http3.CapsuleType = 0x2843
maxDataCapsuleType http3.CapsuleType = 0x190b4d3d
maxStreamDataCapsuleType http3.CapsuleType = 0x190b4d3e // only used on WebTransport over HTTP/2
maxStreamsBidiCapsuleType http3.CapsuleType = 0x190b4d3f
maxStreamsUniCapsuleType http3.CapsuleType = 0x190b4d40
dataBlockedCapsuleType http3.CapsuleType = 0x190b4d41
streamDataBlockedCapsuleType http3.CapsuleType = 0x190b4d42 // only used on WebTransport over HTTP/2
streamsBlockedBidiCapsuleType http3.CapsuleType = 0x190b4d43
streamsBlockedUniCapsuleType http3.CapsuleType = 0x190b4d44
)
const maxCloseCapsuleErrorMsgLen = 1024
const maxStreamsLimit = 1 << 60
type capsule interface {
Append([]byte) []byte
}
// parseNextCapsule parses Capsules sent on the request stream.
// It returns the next known Capsule, skipping unknown Capsules.
func parseNextCapsule(parser *http3.CapsuleParser) (capsule, error) {
for {
typ, capsuleReader, err := parser.Next()
if err != nil {
return nil, err
}
switch typ {
case closeSessionCapsuleType:
capsule, err := parseCloseSessionCapsule(capsuleReader)
if err != nil {
return nil, err
}
return capsule, nil
case maxDataCapsuleType:
maxData, err := parseMaxDataCapsule(capsuleReader)
if err != nil {
return nil, err
}
return maxDataCapsule{MaximumData: maxData}, nil
case maxStreamDataCapsuleType:
return nil, errors.New("WT_MAX_STREAM_DATA capsule received")
case maxStreamsBidiCapsuleType:
maxStreams, err := parseMaxStreamsCapsule(capsuleReader)
if err != nil {
return nil, err
}
return maxStreamsBidiCapsule{MaximumStreams: maxStreams}, nil
case maxStreamsUniCapsuleType:
maxStreams, err := parseMaxStreamsCapsule(capsuleReader)
if err != nil {
return nil, err
}
return maxStreamsUniCapsule{MaximumStreams: maxStreams}, nil
case dataBlockedCapsuleType:
maxData, err := parseDataBlockedCapsule(capsuleReader)
if err != nil {
return nil, err
}
return dataBlockedCapsule{MaximumData: maxData}, nil
case streamDataBlockedCapsuleType:
return nil, errors.New("WT_STREAM_DATA_BLOCKED capsule received")
case streamsBlockedBidiCapsuleType:
maxStreams, err := parseStreamsBlockedCapsule(capsuleReader)
if err != nil {
return nil, err
}
return streamsBlockedBidiCapsule{MaximumStreams: maxStreams}, nil
case streamsBlockedUniCapsuleType:
maxStreams, err := parseStreamsBlockedCapsule(capsuleReader)
if err != nil {
return nil, err
}
return streamsBlockedUniCapsule{MaximumStreams: maxStreams}, nil
default:
// unknown capsule, skip it
if err := capsuleReader.Discard(); err != nil {
return nil, err
}
}
}
}
type closeSessionCapsule struct {
ErrorCode SessionErrorCode
Message string
}
func parseCloseSessionCapsule(r http3.CapsuleReader) (closeSessionCapsule, error) {
var b [4]byte
if _, err := io.ReadFull(r, b[:]); err != nil {
return closeSessionCapsule{}, err
}
msg, err := io.ReadAll(io.LimitReader(r, maxCloseCapsuleErrorMsgLen))
if err != nil {
return closeSessionCapsule{}, err
}
if err := r.Discard(); err != nil {
return closeSessionCapsule{}, err
}
return closeSessionCapsule{
ErrorCode: SessionErrorCode(binary.BigEndian.Uint32(b[:])),
Message: string(msg),
}, nil
}
func (c closeSessionCapsule) Append(b []byte) []byte {
msg := c.Message
if len(msg) > maxCloseCapsuleErrorMsgLen {
msg = truncateUTF8(msg, maxCloseCapsuleErrorMsgLen)
}
b = quicvarint.Append(b, uint64(closeSessionCapsuleType))
b = quicvarint.Append(b, uint64(4+len(msg)))
payloadStart := len(b)
b = append(b, 0, 0, 0, 0)
binary.BigEndian.PutUint32(b[payloadStart:], uint32(c.ErrorCode))
return append(b, msg...)
}
func (c closeSessionCapsule) ToSessionError() *SessionError {
return &SessionError{
Remote: true,
ErrorCode: c.ErrorCode,
Message: c.Message,
}
}
type maxDataCapsule struct {
MaximumData uint64
}
func (c maxDataCapsule) Append(b []byte) []byte {
b = quicvarint.Append(b, uint64(maxDataCapsuleType))
b = quicvarint.Append(b, uint64(quicvarint.Len(c.MaximumData)))
return quicvarint.Append(b, c.MaximumData)
}
type maxStreamsBidiCapsule struct {
MaximumStreams uint64
}
func (c maxStreamsBidiCapsule) Append(b []byte) []byte {
b = quicvarint.Append(b, uint64(maxStreamsBidiCapsuleType))
b = quicvarint.Append(b, uint64(quicvarint.Len(c.MaximumStreams)))
return quicvarint.Append(b, c.MaximumStreams)
}
type maxStreamsUniCapsule struct {
MaximumStreams uint64
}
func (c maxStreamsUniCapsule) Append(b []byte) []byte {
b = quicvarint.Append(b, uint64(maxStreamsUniCapsuleType))
b = quicvarint.Append(b, uint64(quicvarint.Len(c.MaximumStreams)))
return quicvarint.Append(b, c.MaximumStreams)
}
type dataBlockedCapsule struct {
MaximumData uint64
}
func (c dataBlockedCapsule) Append(b []byte) []byte {
b = quicvarint.Append(b, uint64(dataBlockedCapsuleType))
b = quicvarint.Append(b, uint64(quicvarint.Len(c.MaximumData)))
return quicvarint.Append(b, c.MaximumData)
}
type streamsBlockedBidiCapsule struct {
MaximumStreams uint64
}
func (c streamsBlockedBidiCapsule) Append(b []byte) []byte {
b = quicvarint.Append(b, uint64(streamsBlockedBidiCapsuleType))
b = quicvarint.Append(b, uint64(quicvarint.Len(c.MaximumStreams)))
return quicvarint.Append(b, c.MaximumStreams)
}
type streamsBlockedUniCapsule struct {
MaximumStreams uint64
}
func (c streamsBlockedUniCapsule) Append(b []byte) []byte {
b = quicvarint.Append(b, uint64(streamsBlockedUniCapsuleType))
b = quicvarint.Append(b, uint64(quicvarint.Len(c.MaximumStreams)))
return quicvarint.Append(b, c.MaximumStreams)
}
func parseDataBlockedCapsule(r http3.CapsuleReader) (uint64, error) {
maxData, err := quicvarint.Read(r)
if err != nil {
return 0, err
}
if r.Remaining() != 0 {
return 0, errors.New("WT_DATA_BLOCKED capsule has trailing data")
}
return maxData, nil
}
func parseMaxDataCapsule(r http3.CapsuleReader) (uint64, error) {
maxData, err := quicvarint.Read(r)
if err != nil {
return 0, err
}
if r.Remaining() != 0 {
return 0, errors.New("WT_MAX_DATA capsule has trailing data")
}
return maxData, nil
}
func parseMaxStreamsCapsule(r http3.CapsuleReader) (uint64, error) {
maxStreams, err := quicvarint.Read(r)
if err != nil {
return 0, err
}
if r.Remaining() != 0 {
return 0, errors.New("WT_MAX_STREAMS capsule has trailing data")
}
if maxStreams > maxStreamsLimit {
return 0, errors.New("WT_MAX_STREAMS value too large")
}
return maxStreams, nil
}
func parseStreamsBlockedCapsule(r http3.CapsuleReader) (uint64, error) {
maxStreams, err := quicvarint.Read(r)
if err != nil {
return 0, err
}
if r.Remaining() != 0 {
return 0, errors.New("WT_STREAMS_BLOCKED capsule has trailing data")
}
if maxStreams > maxStreamsLimit {
return 0, errors.New("WT_STREAMS_BLOCKED value too large")
}
return maxStreams, nil
}
// truncateUTF8 cuts a string to max n bytes without breaking UTF-8 characters.
func truncateUTF8(s string, n int) string {
if len(s) <= n {
return s
}
for n > 0 && !utf8.RuneStart(s[n]) {
n--
}
return s[:n]
}