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.
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