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,528 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Raioquic
|
4
|
+
module Quic
|
5
|
+
module Recovery
|
6
|
+
# loss detection
|
7
|
+
K_PACKET_THRESHOLD = 3
|
8
|
+
K_GRANULARITY = 0.001 # seconds
|
9
|
+
K_TIME_THRESHOLD = 9 / 8.0
|
10
|
+
K_MICRO_SECOND = 0.000001
|
11
|
+
K_SECOND = 1.0
|
12
|
+
|
13
|
+
# congestion control
|
14
|
+
K_MAX_DATAGRAM_SIZE = 1280
|
15
|
+
K_INITIAL_WINDOW = 10 * K_MAX_DATAGRAM_SIZE
|
16
|
+
K_MINIMUM_WINDOW = 2 * K_MAX_DATAGRAM_SIZE
|
17
|
+
K_LOSS_REDUCTION_FACTOR = 0.5
|
18
|
+
|
19
|
+
# QuicPacketSpace
|
20
|
+
class QuicPacketSpace
|
21
|
+
attr_accessor :sent_packets
|
22
|
+
attr_accessor :ack_at
|
23
|
+
attr_accessor :ack_eliciting_in_flight
|
24
|
+
attr_accessor :loss_time
|
25
|
+
attr_accessor :largest_acked_packet
|
26
|
+
attr_accessor :expected_packet_number
|
27
|
+
attr_accessor :largest_received_packet
|
28
|
+
attr_accessor :largest_received_time
|
29
|
+
attr_accessor :ack_queue
|
30
|
+
attr_accessor :discarded
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
@ack_at = nil
|
34
|
+
@ack_queue = Rangeset.new
|
35
|
+
@discarded = false
|
36
|
+
@expected_packet_number = 0
|
37
|
+
@largest_received_packet = -1
|
38
|
+
@largest_received_time = nil
|
39
|
+
|
40
|
+
# sent packets and loss
|
41
|
+
@ack_eliciting_in_flight = 0
|
42
|
+
@largest_acked_packet = 0
|
43
|
+
@loss_time = nil
|
44
|
+
@sent_packets = {}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# QuicPacketPacer
|
49
|
+
class QuicPacketPacer
|
50
|
+
attr_reader :bucket_max
|
51
|
+
attr_reader :bucket_time
|
52
|
+
attr_reader :packet_time
|
53
|
+
|
54
|
+
def initialize
|
55
|
+
@bucket_max = 0.0
|
56
|
+
@bucket_time = 0.0
|
57
|
+
@evaluation_time = 0.0
|
58
|
+
@packet_time = nil
|
59
|
+
end
|
60
|
+
|
61
|
+
def next_send_time(now:)
|
62
|
+
if @packet_time
|
63
|
+
update_bucket(now: now)
|
64
|
+
return now + @packet_time if @bucket_time <= 0.0
|
65
|
+
end
|
66
|
+
return nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def update_after_send(now:)
|
70
|
+
if @packet_time # rubocop:disable Style/GuardClause
|
71
|
+
update_bucket(now: now)
|
72
|
+
if @bucket_time < @packet_time
|
73
|
+
@bucket_time = 0.0
|
74
|
+
else
|
75
|
+
@bucket_time -= @packet_time
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def update_bucket(now:)
|
81
|
+
if now > @evaluation_time # rubocop:disable Style/GuardClause
|
82
|
+
@bucket_time = [@bucket_time + (now - @evaluation_time), @bucket_max].min
|
83
|
+
@evaluation_time = now
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def update_rate(congestion_window:, smoothed_rtt:)
|
88
|
+
pacing_rate = congestion_window / [smoothed_rtt, K_MICRO_SECOND].max
|
89
|
+
@packet_time = [K_MICRO_SECOND, [K_MAX_DATAGRAM_SIZE / pacing_rate, K_SECOND].min].max
|
90
|
+
|
91
|
+
@bucket_max = [2 * K_MAX_DATAGRAM_SIZE, [(congestion_window / 4).floor(1), 16 * K_MAX_DATAGRAM_SIZE].min].max / pacing_rate
|
92
|
+
|
93
|
+
@bucket_time = @bucket_max if @bucket_time > @bucket_max
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# New Reno congestion control.
|
98
|
+
class QuicCongestionControl
|
99
|
+
attr_reader :congestion_window
|
100
|
+
attr_reader :ssthresh
|
101
|
+
|
102
|
+
attr_accessor :bytes_in_flight
|
103
|
+
|
104
|
+
def initialize
|
105
|
+
@bytes_in_flight = 0
|
106
|
+
@congestion_window = K_INITIAL_WINDOW
|
107
|
+
@congestion_recovery_start_time = 0.0
|
108
|
+
@congestion_stash = 0
|
109
|
+
@rtt_monitor = QuicRttMonitor.new
|
110
|
+
@ssthresh = nil
|
111
|
+
end
|
112
|
+
|
113
|
+
def on_packet_acked(packet:)
|
114
|
+
@bytes_in_flight -= packet.sent_bytes
|
115
|
+
|
116
|
+
# don't increase window in congestion recovery
|
117
|
+
return if packet.sent_time <= @congestion_recovery_start_time
|
118
|
+
|
119
|
+
if @ssthresh.nil? || @congestion_window < @ssthresh
|
120
|
+
# slow start
|
121
|
+
@congestion_window += packet.sent_bytes
|
122
|
+
else
|
123
|
+
# congestion avoidance
|
124
|
+
@congestion_stash += packet.sent_bytes
|
125
|
+
count = (@congestion_stash / @congestion_window.to_f).floor(1)
|
126
|
+
if count > 0.0
|
127
|
+
@congestion_stash -= count * @congestion_window
|
128
|
+
@congestion_window += count * K_MAX_DATAGRAM_SIZE
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def on_packet_sent(packet:)
|
134
|
+
@bytes_in_flight += packet.sent_bytes
|
135
|
+
end
|
136
|
+
|
137
|
+
def on_packets_expired(packets:)
|
138
|
+
packets.each { |packet| @bytes_in_flight -= packet.sent_bytes }
|
139
|
+
end
|
140
|
+
|
141
|
+
def on_packets_lost(packets:, now:)
|
142
|
+
lost_largest_time = 0.0
|
143
|
+
packets.each do |packet|
|
144
|
+
@bytes_in_flight -= packet.sent_bytes
|
145
|
+
lost_largest_time = packet.sent_time
|
146
|
+
end
|
147
|
+
|
148
|
+
# start a new congestion event if packet was sent after the
|
149
|
+
# start of the previous congestioon recovery period.
|
150
|
+
if lost_largest_time > @congestion_recovery_start_time # rubocop:disable Style/GuardClause
|
151
|
+
@congestion_recovery_start_time = now
|
152
|
+
@congestion_window = [(@congestion_window * K_LOSS_REDUCTION_FACTOR).to_i, K_MINIMUM_WINDOW].max
|
153
|
+
@ssthresh = @congestion_window
|
154
|
+
end
|
155
|
+
# TODO: collapse congestion window if persistent congestion (from aioquic)
|
156
|
+
end
|
157
|
+
|
158
|
+
def on_rtt_measurement(latest_rtt:, now:)
|
159
|
+
if @ssthresh.nil? && @rtt_monitor.is_rtt_increasing(rtt: latest_rtt, now: now) # rubocop:disable Style/GuardClause
|
160
|
+
@ssthresh = @congestion_window
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Packet loss and congestion controller.
|
166
|
+
class QuicPacketRecovery
|
167
|
+
attr_reader :rtt_initialized
|
168
|
+
attr_reader :rtt_latest
|
169
|
+
attr_reader :rtt_min
|
170
|
+
attr_reader :rtt_smoothed
|
171
|
+
attr_reader :cc
|
172
|
+
|
173
|
+
attr_accessor :spaces
|
174
|
+
attr_accessor :peer_completed_address_validation
|
175
|
+
attr_accessor :pacer
|
176
|
+
attr_accessor :max_ack_delay
|
177
|
+
|
178
|
+
def initialize(initial_rtt:, peer_completed_address_validation:, send_probe: nil, logger: nil, quic_logger: nil)
|
179
|
+
@max_ack_delay = 0.025
|
180
|
+
@peer_completed_address_validation = peer_completed_address_validation
|
181
|
+
@spaces = []
|
182
|
+
|
183
|
+
# callbacks
|
184
|
+
@logger = logger
|
185
|
+
@quic_logger = quic_logger
|
186
|
+
@send_probe = send_probe
|
187
|
+
|
188
|
+
# loss detection
|
189
|
+
@pto_count = 0
|
190
|
+
@rtt_initial = initial_rtt
|
191
|
+
@rtt_initialized = false
|
192
|
+
@rtt_latest = 0.0
|
193
|
+
@rtt_min = Float::INFINITY
|
194
|
+
@rtt_smoothed = 0.0
|
195
|
+
@rtt_variance = 0.0
|
196
|
+
@time_of_last_sent_ack_eliciting_packet = 0.0
|
197
|
+
|
198
|
+
# congestion control
|
199
|
+
@cc = QuicCongestionControl.new
|
200
|
+
@pacer = QuicPacketPacer.new
|
201
|
+
end
|
202
|
+
|
203
|
+
def bytes_in_flight = @cc.bytes_in_flight
|
204
|
+
|
205
|
+
def congestion_window = @cc.congestion_window
|
206
|
+
|
207
|
+
def discard_space(space:)
|
208
|
+
raise ArgumentError unless @spaces.include?(space)
|
209
|
+
|
210
|
+
@cc.on_packets_expired(packets: space.sent_packets.values.filter(&:in_flight))
|
211
|
+
space.sent_packets.clear
|
212
|
+
space.ack_at = nil
|
213
|
+
space.ack_eliciting_in_flight = 0
|
214
|
+
space.loss_time = nil
|
215
|
+
|
216
|
+
# rest PTO count
|
217
|
+
@pto_count = 0
|
218
|
+
|
219
|
+
# TODO: logger
|
220
|
+
end
|
221
|
+
|
222
|
+
def get_loss_detection_time
|
223
|
+
# loss timer
|
224
|
+
loss_space = get_loss_space
|
225
|
+
return loss_space.loss_time if loss_space
|
226
|
+
|
227
|
+
# packet timer
|
228
|
+
if !peer_completed_address_validation || @spaces.sum(&:ack_eliciting_in_flight) > 0
|
229
|
+
timeout = get_probe_timeout * (2**@pto_count)
|
230
|
+
return @time_of_last_sent_ack_eliciting_packet + timeout
|
231
|
+
end
|
232
|
+
|
233
|
+
return nil
|
234
|
+
end
|
235
|
+
|
236
|
+
def get_probe_timeout
|
237
|
+
return 2 * @rtt_initial unless @rtt_initialized
|
238
|
+
|
239
|
+
return @rtt_smoothed + [4 * @rtt_variance, K_GRANULARITY].max + @max_ack_delay
|
240
|
+
end
|
241
|
+
|
242
|
+
# Update metrics as the result of an ACK being received.
|
243
|
+
def on_ack_received(space:, ack_rangeset:, ack_delay:, now:) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
|
244
|
+
is_ack_eliciting = false
|
245
|
+
largest_acked = ack_rangeset.bounds.last - 1
|
246
|
+
largest_newly_acked = nil
|
247
|
+
largest_sent_time = nil
|
248
|
+
log_rtt = nil
|
249
|
+
|
250
|
+
space.largest_acked_packet = largest_acked if largest_acked > space.largest_acked_packet
|
251
|
+
|
252
|
+
space.sent_packets.keys.sort.each do |packet_number|
|
253
|
+
break if packet_number > largest_acked
|
254
|
+
|
255
|
+
if ack_rangeset.in?(packet_number) # rubocop:disable Style/Next
|
256
|
+
packet = space.sent_packets.delete(packet_number)
|
257
|
+
|
258
|
+
if packet.is_ack_eliciting
|
259
|
+
is_ack_eliciting = true
|
260
|
+
space.ack_eliciting_in_flight -= 1
|
261
|
+
end
|
262
|
+
|
263
|
+
@cc.on_packet_acked(packet: packet) if packet.in_flight
|
264
|
+
|
265
|
+
largest_newly_acked = packet_number
|
266
|
+
largest_sent_time = packet.sent_time
|
267
|
+
|
268
|
+
# trigger callbacks
|
269
|
+
packet.delivery_handlers&.each do |handler|
|
270
|
+
# TODO: hmm...
|
271
|
+
delivery = Quic::PacketBuilder::QuicDeliveryState::ACKED
|
272
|
+
case handler[0]&.name
|
273
|
+
when :on_data_delivery
|
274
|
+
handler[0].call(delivery: delivery, start: handler[1][0], stop: handler[1][1])
|
275
|
+
when :on_ack_delivery
|
276
|
+
handler[0].call(delivery: delivery, space: handler[1][0], highest_acked: handler[1][1])
|
277
|
+
when :on_new_connection_id_delivery
|
278
|
+
handler[0].call(delivery: delivery, connection_id: handler[1][0])
|
279
|
+
when :on_handshake_done_delivery, :on_reset_delivery, :on_stop_sending_delivery
|
280
|
+
handler[0].call(delivery: delivery)
|
281
|
+
when :on_ping_delivery
|
282
|
+
handler[0].call(delivery: delivery, uids: handler[1][0])
|
283
|
+
when :on_connection_limit_delivery
|
284
|
+
handler[0].call(delivery: delivery, limit: handler[1][0])
|
285
|
+
when :on_retire_connection_id_delivery
|
286
|
+
handler[0].call(delivery: delivery, sequence_number: handler[1][0])
|
287
|
+
else
|
288
|
+
raise NotImplementedError, handler[0]
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
return if largest_newly_acked.nil?
|
295
|
+
|
296
|
+
if largest_acked == largest_newly_acked && is_ack_eliciting
|
297
|
+
latest_rtt = now - largest_sent_time
|
298
|
+
log_rtt = true
|
299
|
+
|
300
|
+
# limit ACK delay to max_ack_delay
|
301
|
+
ack_delay = [ack_delay, @max_ack_delay].min
|
302
|
+
|
303
|
+
# update RTT estimate, which cannot be < 1 ms
|
304
|
+
@rtt_latest = [latest_rtt, 0.001].max
|
305
|
+
@rtt_min = @rtt_latest if @rtt_latest < @rtt_min
|
306
|
+
@rtt_latest -= ack_delay if @rtt_latest > @rtt_min + ack_delay
|
307
|
+
|
308
|
+
if !@rtt_initialized # rubocop:disable Style/NegatedIfElseCondition
|
309
|
+
@rtt_initialized = true
|
310
|
+
@rtt_variance = latest_rtt / 2.0
|
311
|
+
@rtt_smoothed = latest_rtt
|
312
|
+
else
|
313
|
+
@rtt_variance = (3.0 / 4.0 * @rtt_variance) + (1 / 4 * (@rtt_min - @rtt_latest).abs)
|
314
|
+
@rtt_smoothed - (7.0 / 8.0 * @rtt_smoothed) + (1 / 8 * @rtt_latest)
|
315
|
+
end
|
316
|
+
|
317
|
+
# inform congestion controller
|
318
|
+
@cc.on_rtt_measurement(latest_rtt: latest_rtt, now: now)
|
319
|
+
@pacer.update_rate(congestion_window: @cc.congestion_window, smoothed_rtt: @rtt_smoothed)
|
320
|
+
else
|
321
|
+
log_rtt = false
|
322
|
+
end
|
323
|
+
|
324
|
+
detect_loss(space: space, now: now)
|
325
|
+
|
326
|
+
# reset PTO count
|
327
|
+
@pto_count = 0
|
328
|
+
|
329
|
+
log_metrics_updated(log_rtt) if @quic_logger
|
330
|
+
end
|
331
|
+
|
332
|
+
def on_loss_detection_timeout(now:)
|
333
|
+
loss_space = get_loss_space
|
334
|
+
if loss_space
|
335
|
+
detect_loss(space: loss_space, now: now)
|
336
|
+
else
|
337
|
+
@pto_count += 1
|
338
|
+
reschedule_data(now: now)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
def on_packet_sent(packet:, space:)
|
343
|
+
space.sent_packets[packet.packet_number] = packet
|
344
|
+
|
345
|
+
space.ack_eliciting_in_flight += 1 if packet.is_ack_eliciting
|
346
|
+
|
347
|
+
if packet.in_flight # rubocop:disable Style/GuardClause
|
348
|
+
@time_of_last_sent_ack_eliciting_packet = packet.sent_time if packet.is_ack_eliciting
|
349
|
+
|
350
|
+
# add packet to bytes in flight
|
351
|
+
@cc.on_packet_sent(packet: packet)
|
352
|
+
log_metrics_updated if @quic_logger
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
# Schedule some data for retransmission.
|
357
|
+
def reschedule_data(now:)
|
358
|
+
# if there is any outstanding CRYPTO, retransmit it
|
359
|
+
crypto_scheduled = false
|
360
|
+
@spaces.each do |space|
|
361
|
+
packets = space.sent_packets.values.filter(&:is_crypto_packet)
|
362
|
+
unless packets.empty?
|
363
|
+
on_packets_lost(packets: packets, space: space, now: now)
|
364
|
+
crypto_scheduled = true
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
# TODO: logger debug if crypto_scheduled && @logger
|
369
|
+
|
370
|
+
# ensure an ACK-eliciting packet is sent
|
371
|
+
@send_probe&.call
|
372
|
+
end
|
373
|
+
|
374
|
+
# Check whether any packets should be declared lost.
|
375
|
+
def detect_loss(space:, now:) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
376
|
+
loss_delay = K_TIME_THRESHOLD * (@rtt_initialized ? [@rtt_latest, @rtt_smoothed].max : @rtt_initial)
|
377
|
+
packet_threshold = space.largest_acked_packet - K_PACKET_THRESHOLD
|
378
|
+
time_threshold = now - loss_delay
|
379
|
+
|
380
|
+
lost_packets = []
|
381
|
+
space.loss_time = nil
|
382
|
+
|
383
|
+
space.sent_packets.each do |packet_number, packet|
|
384
|
+
break if packet_number > space.largest_acked_packet
|
385
|
+
|
386
|
+
if packet_number <= packet_threshold || packet.sent_time <= time_threshold
|
387
|
+
lost_packets << packet
|
388
|
+
else
|
389
|
+
packet_loss_time = packet.sent_time + loss_delay
|
390
|
+
space.loss_time = packet_loss_time.to_f if space.loss_time.nil? || space.loss_time > packet_loss_time
|
391
|
+
end
|
392
|
+
end
|
393
|
+
on_packets_lost(packets: lost_packets, space: space, now: now)
|
394
|
+
end
|
395
|
+
|
396
|
+
def get_loss_space
|
397
|
+
loss_space = nil
|
398
|
+
@spaces.each do |space|
|
399
|
+
loss_space = space if space.loss_time && (loss_space.nil? || space.loss_time < loss_space.loss_time)
|
400
|
+
end
|
401
|
+
return loss_space
|
402
|
+
end
|
403
|
+
|
404
|
+
def log_metrics_updated(log_rtt = false) # rubocop:disable Style/OptionalBooleanParameter
|
405
|
+
data = {
|
406
|
+
bytes_in_flight: @cc.bytes_in_flight,
|
407
|
+
cwnd: @cc.congestion_window,
|
408
|
+
}
|
409
|
+
data[:ssthresh] = @cc.ssthresh if @cc.ssthresh
|
410
|
+
if log_rtt # rubocop:disable Style/GuardClause
|
411
|
+
data[:latest_rtt] = @quic_logger&.encode_time(@rtt_latest)
|
412
|
+
data[:min_rtt] = @quic_logger&.encode_time(@rtt_min)
|
413
|
+
data[:smoothed_rtt] = @quic_logger&.encode_time(@rtt_smoothed)
|
414
|
+
data[:rtt_variance] = @quic_logger&.encode_time(@rtt_variance)
|
415
|
+
end
|
416
|
+
|
417
|
+
@quic_logger&.log_event(category: "recovery", event: "metrics_updated", data: data)
|
418
|
+
end
|
419
|
+
|
420
|
+
def on_packets_lost(packets:, space:, now:) # rubocop:disable Metrics/CyclomaticComplexity
|
421
|
+
lost_packets_cc = []
|
422
|
+
packets.each do |packet|
|
423
|
+
space.sent_packets.delete(packet.packet_number)
|
424
|
+
|
425
|
+
lost_packets_cc << packet if packet.in_flight
|
426
|
+
space.ack_eliciting_in_flight -= 1 if packet.is_ack_eliciting
|
427
|
+
|
428
|
+
if @quic_logger
|
429
|
+
@quic_logger.log_event(
|
430
|
+
category: "recovery",
|
431
|
+
event: "packet_lost",
|
432
|
+
data: {
|
433
|
+
type: @quic_logger.packet_type(packet.packet_type),
|
434
|
+
packet_number: packet.packet_number,
|
435
|
+
},
|
436
|
+
)
|
437
|
+
end
|
438
|
+
|
439
|
+
# trigger callbacks
|
440
|
+
packet.delivery_handlers&.each do |handler|
|
441
|
+
# TODO: hmm...
|
442
|
+
case handler[0]&.name
|
443
|
+
when :on_data_delivery
|
444
|
+
handler[0].call(delivery: Quic::PacketBuilder::QuicDeliveryState::LOST, start: handler[1][0], stop: handler[1][1])
|
445
|
+
when :on_ack_delivery
|
446
|
+
handler[0].call(delivery: Quic::PacketBuilder::QuicDeliveryState::LOST, space: handler[1][0], highest_acked: handler[1][1])
|
447
|
+
when :on_handshake_done_delivery
|
448
|
+
handler[0].call(delivery: Quic::PacketBuilder::QuicDeliveryState::LOST)
|
449
|
+
when :on_new_connection_id_delivery
|
450
|
+
handler[0].call(delivery: Quic::PacketBuilder::QuicDeliveryState::LOST, connection_id: handler[1][0])
|
451
|
+
when :on_connection_limit_delivery
|
452
|
+
handler[0].call(delivery: Quic::PacketBuilder::QuicDeliveryState::LOST, limit: handler[1][0])
|
453
|
+
else
|
454
|
+
raise NotImplementedError, handler[0]
|
455
|
+
end
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
# inform congestion controller
|
460
|
+
if lost_packets_cc # rubocop:disable Style/GuardClause
|
461
|
+
@cc.on_packets_lost(packets: lost_packets_cc, now: now)
|
462
|
+
@pacer.update_rate(congestion_window: @cc.congestion_window, smoothed_rtt: @rtt_smoothed)
|
463
|
+
log_metrics_updated if @quic_logger
|
464
|
+
end
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
# Roundtrip time monitor for HyStart.
|
469
|
+
class QuicRttMonitor
|
470
|
+
attr_reader :samples
|
471
|
+
attr_reader :ready
|
472
|
+
attr_reader :increases
|
473
|
+
|
474
|
+
def initialize
|
475
|
+
@increases = 0
|
476
|
+
@last_time = nil
|
477
|
+
@ready = false
|
478
|
+
@size = 5
|
479
|
+
@filtered_min = nil
|
480
|
+
@sample_idx = 0
|
481
|
+
@sample_max = nil
|
482
|
+
@sample_min = nil
|
483
|
+
@sample_time = 0.0
|
484
|
+
@samples = @size.times.map { 0.0 }
|
485
|
+
end
|
486
|
+
|
487
|
+
def add_rtt(rtt:)
|
488
|
+
@samples[@sample_idx] = rtt
|
489
|
+
@sample_idx += 1
|
490
|
+
|
491
|
+
if @sample_idx >= @size
|
492
|
+
@sample_idx = 0
|
493
|
+
@ready = true
|
494
|
+
end
|
495
|
+
|
496
|
+
if @ready # rubocop:disable Style/GuardClause
|
497
|
+
@sample_max = @samples[0]
|
498
|
+
@sample_min = @samples[0]
|
499
|
+
@samples[1..].each do |sample|
|
500
|
+
@sample_min = sample if sample < @sample_min
|
501
|
+
@sample_max = sample if sample > @sample_max
|
502
|
+
end
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
def is_rtt_increasing(rtt:, now:) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
507
|
+
if now > @sample_time + K_GRANULARITY
|
508
|
+
add_rtt(rtt: rtt)
|
509
|
+
@sample_time = now
|
510
|
+
|
511
|
+
if @ready
|
512
|
+
@filtered_min = @sample_max if @filtered_min.nil? || @filtered_min > @sample_max
|
513
|
+
|
514
|
+
delta = @sample_min - @filtered_min
|
515
|
+
if delta * 4 >= @filtered_min
|
516
|
+
@increases += 1
|
517
|
+
return true if @increases >= @size # rubocop:disable Metrics/BlockNesting
|
518
|
+
elsif delta > 0
|
519
|
+
@increases = 0
|
520
|
+
end
|
521
|
+
end
|
522
|
+
end
|
523
|
+
return false
|
524
|
+
end
|
525
|
+
end
|
526
|
+
end
|
527
|
+
end
|
528
|
+
end
|