raioquic 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/.containerignore +4 -0
  3. data/.rubocop.yml +93 -0
  4. data/CHANGELOG.md +5 -0
  5. data/CODE_OF_CONDUCT.md +84 -0
  6. data/Containerfile +6 -0
  7. data/Gemfile +24 -0
  8. data/Gemfile.lock +113 -0
  9. data/LICENSE +28 -0
  10. data/README.md +48 -0
  11. data/Rakefile +16 -0
  12. data/Steepfile +8 -0
  13. data/example/curlcatcher.rb +18 -0
  14. data/example/interoperability/README.md +9 -0
  15. data/example/interoperability/aioquic/aioquic_client.py +47 -0
  16. data/example/interoperability/aioquic/aioquic_server.py +34 -0
  17. data/example/interoperability/key.pem +28 -0
  18. data/example/interoperability/localhost-unasuke-dev.crt +21 -0
  19. data/example/interoperability/quic-go/sample_server.go +61 -0
  20. data/example/interoperability/raioquic_client.rb +42 -0
  21. data/example/interoperability/raioquic_server.rb +43 -0
  22. data/example/parse_curl_example.rb +108 -0
  23. data/lib/raioquic/buffer.rb +202 -0
  24. data/lib/raioquic/core_ext.rb +54 -0
  25. data/lib/raioquic/crypto/README.md +5 -0
  26. data/lib/raioquic/crypto/aesgcm.rb +52 -0
  27. data/lib/raioquic/crypto/backend/aead.rb +52 -0
  28. data/lib/raioquic/crypto/backend.rb +12 -0
  29. data/lib/raioquic/crypto.rb +10 -0
  30. data/lib/raioquic/quic/configuration.rb +81 -0
  31. data/lib/raioquic/quic/connection.rb +2776 -0
  32. data/lib/raioquic/quic/crypto.rb +317 -0
  33. data/lib/raioquic/quic/event.rb +69 -0
  34. data/lib/raioquic/quic/logger.rb +272 -0
  35. data/lib/raioquic/quic/packet.rb +471 -0
  36. data/lib/raioquic/quic/packet_builder.rb +301 -0
  37. data/lib/raioquic/quic/rangeset.rb +113 -0
  38. data/lib/raioquic/quic/recovery.rb +528 -0
  39. data/lib/raioquic/quic/stream.rb +343 -0
  40. data/lib/raioquic/quic.rb +20 -0
  41. data/lib/raioquic/tls.rb +1659 -0
  42. data/lib/raioquic/version.rb +5 -0
  43. data/lib/raioquic.rb +12 -0
  44. data/misc/export_x25519.py +43 -0
  45. data/misc/gen_rfc8448_keypair.rb +90 -0
  46. data/raioquic.gemspec +37 -0
  47. data/sig/raioquic/buffer.rbs +37 -0
  48. data/sig/raioquic/core_ext.rbs +7 -0
  49. data/sig/raioquic/crypto/aesgcm.rbs +20 -0
  50. data/sig/raioquic/crypto/backend/aead.rbs +11 -0
  51. data/sig/raioquic/quic/configuration.rbs +34 -0
  52. data/sig/raioquic/quic/connection.rbs +277 -0
  53. data/sig/raioquic/quic/crypto.rbs +88 -0
  54. data/sig/raioquic/quic/event.rbs +51 -0
  55. data/sig/raioquic/quic/logger.rbs +57 -0
  56. data/sig/raioquic/quic/packet.rbs +157 -0
  57. data/sig/raioquic/quic/packet_builder.rbs +76 -0
  58. data/sig/raioquic/quic/rangeset.rbs +17 -0
  59. data/sig/raioquic/quic/recovery.rbs +142 -0
  60. data/sig/raioquic/quic/stream.rbs +87 -0
  61. data/sig/raioquic/tls.rbs +444 -0
  62. data/sig/raioquic.rbs +9 -0
  63. 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