raioquic 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|