raioquic 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.containerignore +4 -0
- data/.rubocop.yml +93 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Containerfile +6 -0
- data/Gemfile +24 -0
- data/Gemfile.lock +113 -0
- data/LICENSE +28 -0
- data/README.md +48 -0
- data/Rakefile +16 -0
- data/Steepfile +8 -0
- data/example/curlcatcher.rb +18 -0
- data/example/interoperability/README.md +9 -0
- data/example/interoperability/aioquic/aioquic_client.py +47 -0
- data/example/interoperability/aioquic/aioquic_server.py +34 -0
- data/example/interoperability/key.pem +28 -0
- data/example/interoperability/localhost-unasuke-dev.crt +21 -0
- data/example/interoperability/quic-go/sample_server.go +61 -0
- data/example/interoperability/raioquic_client.rb +42 -0
- data/example/interoperability/raioquic_server.rb +43 -0
- data/example/parse_curl_example.rb +108 -0
- data/lib/raioquic/buffer.rb +202 -0
- data/lib/raioquic/core_ext.rb +54 -0
- data/lib/raioquic/crypto/README.md +5 -0
- data/lib/raioquic/crypto/aesgcm.rb +52 -0
- data/lib/raioquic/crypto/backend/aead.rb +52 -0
- data/lib/raioquic/crypto/backend.rb +12 -0
- data/lib/raioquic/crypto.rb +10 -0
- data/lib/raioquic/quic/configuration.rb +81 -0
- data/lib/raioquic/quic/connection.rb +2776 -0
- data/lib/raioquic/quic/crypto.rb +317 -0
- data/lib/raioquic/quic/event.rb +69 -0
- data/lib/raioquic/quic/logger.rb +272 -0
- data/lib/raioquic/quic/packet.rb +471 -0
- data/lib/raioquic/quic/packet_builder.rb +301 -0
- data/lib/raioquic/quic/rangeset.rb +113 -0
- data/lib/raioquic/quic/recovery.rb +528 -0
- data/lib/raioquic/quic/stream.rb +343 -0
- data/lib/raioquic/quic.rb +20 -0
- data/lib/raioquic/tls.rb +1659 -0
- data/lib/raioquic/version.rb +5 -0
- data/lib/raioquic.rb +12 -0
- data/misc/export_x25519.py +43 -0
- data/misc/gen_rfc8448_keypair.rb +90 -0
- data/raioquic.gemspec +37 -0
- data/sig/raioquic/buffer.rbs +37 -0
- data/sig/raioquic/core_ext.rbs +7 -0
- data/sig/raioquic/crypto/aesgcm.rbs +20 -0
- data/sig/raioquic/crypto/backend/aead.rbs +11 -0
- data/sig/raioquic/quic/configuration.rbs +34 -0
- data/sig/raioquic/quic/connection.rbs +277 -0
- data/sig/raioquic/quic/crypto.rbs +88 -0
- data/sig/raioquic/quic/event.rbs +51 -0
- data/sig/raioquic/quic/logger.rbs +57 -0
- data/sig/raioquic/quic/packet.rbs +157 -0
- data/sig/raioquic/quic/packet_builder.rbs +76 -0
- data/sig/raioquic/quic/rangeset.rbs +17 -0
- data/sig/raioquic/quic/recovery.rbs +142 -0
- data/sig/raioquic/quic/stream.rbs +87 -0
- data/sig/raioquic/tls.rbs +444 -0
- data/sig/raioquic.rbs +9 -0
- metadata +121 -0
@@ -0,0 +1,343 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Raioquic
|
4
|
+
module Quic
|
5
|
+
module Stream
|
6
|
+
class FinalSizeError < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
class StreamFinishedError < StandardError
|
10
|
+
end
|
11
|
+
|
12
|
+
# The receive part of a QUIC stream.
|
13
|
+
#
|
14
|
+
# It finishes:
|
15
|
+
# - immediately for a send-only stream
|
16
|
+
# - upon reception of a aSTREAM_RESET frame
|
17
|
+
# - upon reception of a data frame with the FIN bit set
|
18
|
+
class QuicStreamReceiver
|
19
|
+
attr_reader :is_finished
|
20
|
+
attr_reader :buffer
|
21
|
+
attr_reader :ranges
|
22
|
+
attr_reader :buffer_start
|
23
|
+
attr_reader :highest_offset
|
24
|
+
attr_reader :stop_pending
|
25
|
+
|
26
|
+
def initialize(stream_id: nil, readable: false) # rubocop:disable Lint/UnusedMethodArgument
|
27
|
+
@highest_offset = 0 # the highest offset ever seen
|
28
|
+
@is_finished = false
|
29
|
+
@stop_pending = false
|
30
|
+
@buffer = +""
|
31
|
+
@buffer_start = 0 # the offset for the start of the buffer
|
32
|
+
@final_size = nil
|
33
|
+
@ranges = Quic::Rangeset.new(ranges: [])
|
34
|
+
@stream_id = stream_id
|
35
|
+
@stop_error_code = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_stop_frame
|
39
|
+
@stop_pending = false
|
40
|
+
return Quic::Packet::QuicStopSendingFrame.new.tap do |frame|
|
41
|
+
frame.error_code = @stop_error_code
|
42
|
+
frame.stream_id = @stream_id
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Handle a frame of received data.
|
47
|
+
def handle_frame(frame:) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
48
|
+
pos = frame.offset - @buffer_start
|
49
|
+
count = frame.data.bytesize
|
50
|
+
frame_end = frame.offset + count
|
51
|
+
|
52
|
+
# we should receive no more data beyond FIN!
|
53
|
+
if @final_size
|
54
|
+
raise FinalSizeError, "Data received beyond final size" if frame_end > @final_size
|
55
|
+
raise FinalSizeError, "Cannot change final size" if frame.fin && frame_end != @final_size
|
56
|
+
end
|
57
|
+
|
58
|
+
@final_size = frame_end if frame.fin
|
59
|
+
@highest_offset = frame_end if frame_end > @highest_offset
|
60
|
+
# binding.b
|
61
|
+
|
62
|
+
# fast path: new in-order chunk
|
63
|
+
if pos == 0 && count > 1 && @buffer.empty?
|
64
|
+
@buffer_start += count
|
65
|
+
# all data up to the FIN has been received, we're done receiving
|
66
|
+
@is_finished = true if frame.fin
|
67
|
+
|
68
|
+
return Quic::Event::StreamDataReceived.new.tap do |event|
|
69
|
+
event.data = frame.data
|
70
|
+
event.end_stream = !!frame.fin # rubocop:disable Style/DoubleNegation ensure true/false
|
71
|
+
event.stream_id = @stream_id
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# discard duplicate data
|
76
|
+
if pos < 0
|
77
|
+
frame.data = frame.data[-pos..] || ""
|
78
|
+
frame.offset -= pos
|
79
|
+
pos = 0
|
80
|
+
count = frame.data.bytesize
|
81
|
+
end
|
82
|
+
|
83
|
+
# marked received range
|
84
|
+
@ranges.add(frame.offset, frame_end) if frame_end > frame.offset
|
85
|
+
|
86
|
+
# add new data
|
87
|
+
gap = pos - @buffer.bytesize
|
88
|
+
@buffer += "\x00" * gap if gap > 0
|
89
|
+
@buffer[pos...(pos + count)] = frame.data
|
90
|
+
|
91
|
+
# return data from the front of the buffer
|
92
|
+
data = pull_data
|
93
|
+
end_stream = @buffer_start == @final_size
|
94
|
+
if end_stream
|
95
|
+
# all data up to the FIN has been received, we're done receiving
|
96
|
+
@is_finished = true
|
97
|
+
end
|
98
|
+
|
99
|
+
if !data.empty? || end_stream # rubocop:disable Style/GuardClause
|
100
|
+
return Quic::Event::StreamDataReceived.new.tap do |event|
|
101
|
+
event.data = data
|
102
|
+
event.end_stream = end_stream
|
103
|
+
event.stream_id = @stream_id
|
104
|
+
end
|
105
|
+
else
|
106
|
+
return nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Handle an abrupt termination of the receiving part of the QUIC stream.
|
111
|
+
def handle_reset(final_size:, error_code: Quic::Packet::QuicErrorCode::NO_ERROR)
|
112
|
+
raise FinalSizeError, "Cannot change final size" if @final_size && final_size != @final_size
|
113
|
+
|
114
|
+
# we are done receiving
|
115
|
+
@final_size = final_size
|
116
|
+
@is_finished = true
|
117
|
+
return Quic::Event::StreamReset.new.tap do |reset|
|
118
|
+
reset.error_code = error_code
|
119
|
+
reset.stream_id = @stream_id
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Callback when a STOP_SENDING is ACK'd.
|
124
|
+
def on_stop_sending_delivery(delivery:)
|
125
|
+
@stop_pending = true if delivery != Quic::PacketBuilder::QuicDeliveryState::ACKED
|
126
|
+
end
|
127
|
+
|
128
|
+
# Request the peer stop sending data on the QUIC stream.
|
129
|
+
def stop(error_code: Quic::Packet::QuicErrorCode::NO_ERROR)
|
130
|
+
@stop_error_code = error_code
|
131
|
+
@stop_pending = true
|
132
|
+
end
|
133
|
+
|
134
|
+
# Remove data from the front of the buffer.
|
135
|
+
def pull_data
|
136
|
+
has_data_to_read = nil
|
137
|
+
begin
|
138
|
+
has_data_to_read = @ranges.list[0].first == @buffer_start
|
139
|
+
rescue IndexError, NoMethodError
|
140
|
+
has_data_to_read = false
|
141
|
+
end
|
142
|
+
return "" unless has_data_to_read
|
143
|
+
|
144
|
+
r = @ranges.shift
|
145
|
+
pos = r.last - r.first
|
146
|
+
data = @buffer[...pos]
|
147
|
+
@buffer[...pos] = ""
|
148
|
+
@buffer_start = r.last
|
149
|
+
return data
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# The send part of a QUIC stream.
|
154
|
+
#
|
155
|
+
# It finishes:
|
156
|
+
# - immediately for a receive-only stream
|
157
|
+
# - upon acknowledgement of a STREAM_RESET frame
|
158
|
+
# - upon acknowledgement of a data frame with the FIN bit set
|
159
|
+
class QuicStreamSender
|
160
|
+
attr_reader :is_finished
|
161
|
+
attr_reader :buffer
|
162
|
+
attr_reader :pending
|
163
|
+
attr_reader :buffer_is_empty
|
164
|
+
attr_reader :reset_pending
|
165
|
+
attr_accessor :highest_offset
|
166
|
+
|
167
|
+
def initialize(stream_id: nil, writable:)
|
168
|
+
@buffer_is_empty = true
|
169
|
+
@highest_offset = 0
|
170
|
+
@is_finished = !writable
|
171
|
+
@reset_pending = false
|
172
|
+
|
173
|
+
@acked = Rangeset.new
|
174
|
+
@buffer = ""
|
175
|
+
@buffer_fin = nil
|
176
|
+
@buffer_start = 0 # the offset for the start of the buffer
|
177
|
+
@buffer_stop = 0 # the offset for the stop of the buffer
|
178
|
+
@pending = Rangeset.new
|
179
|
+
@pending_eof = false
|
180
|
+
@reset_error_code = nil
|
181
|
+
@stream_id = stream_id
|
182
|
+
end
|
183
|
+
|
184
|
+
# The offset for the next frame to send.
|
185
|
+
#
|
186
|
+
# This is used to determine the space needed for the frame's `offset` field.
|
187
|
+
def next_offset
|
188
|
+
return @pending.list[0].first
|
189
|
+
rescue IndexError, NoMethodError
|
190
|
+
return @buffer_stop
|
191
|
+
end
|
192
|
+
|
193
|
+
# Get a frame of data to send.
|
194
|
+
def get_frame(max_size:, max_offset: nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
195
|
+
# get the first pending data range
|
196
|
+
r = nil
|
197
|
+
begin
|
198
|
+
raise IndexError if @pending.list.empty?
|
199
|
+
|
200
|
+
r = @pending.list[0]
|
201
|
+
rescue IndexError
|
202
|
+
if @pending_eof
|
203
|
+
# FIN only
|
204
|
+
@pending_eof = false
|
205
|
+
return Quic::Packet::QuicStreamFrame.new.tap do |frame|
|
206
|
+
frame.fin = true
|
207
|
+
frame.offset = @buffer_fin
|
208
|
+
frame.data = ""
|
209
|
+
end
|
210
|
+
end
|
211
|
+
@buffer_is_empty = true
|
212
|
+
return nil
|
213
|
+
end
|
214
|
+
|
215
|
+
# apply flow control
|
216
|
+
start = r.first
|
217
|
+
stop = [r.last, start + max_size].min
|
218
|
+
stop = max_offset if max_offset && stop > max_offset
|
219
|
+
return nil if stop <= start
|
220
|
+
|
221
|
+
# create frame
|
222
|
+
frame = Quic::Packet::QuicStreamFrame.new.tap do |f|
|
223
|
+
f.data = @buffer[(start - @buffer_start)...(stop - @buffer_start)] || ""
|
224
|
+
f.offset = start
|
225
|
+
f.fin = false
|
226
|
+
end
|
227
|
+
@pending.subtract(start, stop)
|
228
|
+
|
229
|
+
# track the highest offset ever sent
|
230
|
+
@highest_offset = stop if stop > @highest_offset
|
231
|
+
|
232
|
+
# if the buffer is empty and EOF was written, set the FIN bit
|
233
|
+
if @buffer_fin == stop
|
234
|
+
frame.fin = true
|
235
|
+
@pending_eof = false
|
236
|
+
end
|
237
|
+
|
238
|
+
return frame
|
239
|
+
end
|
240
|
+
|
241
|
+
def get_reset_frame
|
242
|
+
@reset_pending = false
|
243
|
+
return Quic::Packet::QuicResetStreamFrame.new.tap do |frame|
|
244
|
+
frame.error_code = @reset_error_code
|
245
|
+
frame.final_size = @highest_offset
|
246
|
+
frame.stream_id = @stream_id
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Callback when sent data is ACK'd
|
251
|
+
def on_data_delivery(delivery:, start:, stop:)
|
252
|
+
@buffer_is_empty = false
|
253
|
+
if delivery == Quic::PacketBuilder::QuicDeliveryState::ACKED
|
254
|
+
if stop > start
|
255
|
+
@acked.add(start, stop)
|
256
|
+
first_range = @acked.list[0]
|
257
|
+
if first_range.first == @buffer_start
|
258
|
+
size = first_range.last - first_range.first
|
259
|
+
@acked.shift
|
260
|
+
@buffer_start += size
|
261
|
+
@buffer[..size] = ""
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
if @buffer_start == @buffer_fin
|
266
|
+
# all data up to the FIN has been ACK'd, we're done sending
|
267
|
+
@is_finished = true
|
268
|
+
end
|
269
|
+
else
|
270
|
+
@pending.add(start, stop) if stop > start
|
271
|
+
if stop == @buffer_fin
|
272
|
+
# @send_buffer_empty = false # doesn't used?
|
273
|
+
@pending_eof = true
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# Callback when a reset is ACK'd.
|
279
|
+
def on_reset_delivery(delivery:)
|
280
|
+
if delivery == Quic::PacketBuilder::QuicDeliveryState::ACKED
|
281
|
+
# the reset has been ACK'd, we're done sending
|
282
|
+
@is_finished = true
|
283
|
+
else
|
284
|
+
@reset_pending = true
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# Abruptly terminate the sending part of the QUIC stream.
|
289
|
+
def reset(error_code:)
|
290
|
+
raise RuntimeError if @reset_error_code # cannot call reset() more than once
|
291
|
+
|
292
|
+
@reset_error_code = error_code
|
293
|
+
@reset_pending = true
|
294
|
+
end
|
295
|
+
|
296
|
+
# Write some data bytes to the QUIC stream.
|
297
|
+
def write(data:, end_stream: false)
|
298
|
+
raise RuntimeError if @buffer_fin # cannnot call write() after FIN
|
299
|
+
raise RuntimeError if @reset_error_code # cannot call write() after reset()
|
300
|
+
|
301
|
+
size = data.bytesize
|
302
|
+
if size > 0
|
303
|
+
@buffer_is_empty = false
|
304
|
+
@pending.add(@buffer_stop, @buffer_stop + size)
|
305
|
+
@buffer += data
|
306
|
+
@buffer_stop += size
|
307
|
+
end
|
308
|
+
|
309
|
+
if end_stream # rubocop:disable Style/GuardClause
|
310
|
+
@buffer_is_empty = false
|
311
|
+
@buffer_fin = @buffer_stop
|
312
|
+
@pending_eof = true
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# Represent QUIC Stream
|
318
|
+
class QuicStream
|
319
|
+
attr_reader :receiver
|
320
|
+
attr_reader :sender
|
321
|
+
attr_reader :stream_id
|
322
|
+
attr_accessor :is_blocked
|
323
|
+
attr_accessor :max_stream_data_local
|
324
|
+
attr_accessor :max_stream_data_local_sent
|
325
|
+
attr_accessor :max_stream_data_remote
|
326
|
+
|
327
|
+
def initialize(stream_id: nil, max_stream_data_local: 0, max_stream_data_remote: 0, readable: true, writable: true)
|
328
|
+
@is_blocked = false
|
329
|
+
@max_stream_data_local = max_stream_data_local
|
330
|
+
@max_stream_data_local_sent = max_stream_data_local
|
331
|
+
@max_stream_data_remote = max_stream_data_remote
|
332
|
+
@receiver = QuicStreamReceiver.new(stream_id: stream_id, readable: readable)
|
333
|
+
@sender = QuicStreamSender.new(stream_id: stream_id, writable: writable)
|
334
|
+
@stream_id = stream_id
|
335
|
+
end
|
336
|
+
|
337
|
+
def is_finished
|
338
|
+
@receiver.is_finished && @sender.is_finished
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "quic/rangeset"
|
4
|
+
require_relative "quic/packet"
|
5
|
+
require_relative "quic/packet_builder"
|
6
|
+
require_relative "quic/crypto"
|
7
|
+
require_relative "quic/configuration"
|
8
|
+
require_relative "quic/recovery"
|
9
|
+
require_relative "quic/event"
|
10
|
+
require_relative "quic/stream"
|
11
|
+
require_relative "quic/connection"
|
12
|
+
require_relative "quic/logger"
|
13
|
+
|
14
|
+
module Raioquic
|
15
|
+
# Raioquic::Quic
|
16
|
+
# Module for QUIC protocol related modules and classes
|
17
|
+
# represent aioquic/src/aioquic/quic directory
|
18
|
+
module Quic
|
19
|
+
end
|
20
|
+
end
|