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,42 @@
1
+ require "raioquic"
2
+ require "socket"
3
+
4
+ HOST = ["0.0.0.0", 4433]
5
+
6
+ conf = Raioquic::Quic::QuicConfiguration.new(is_client: true)
7
+ conf.load_verify_locations(cafile: "localhost-unasuke-dev.crt")
8
+ client = Raioquic::Quic::Connection::QuicConnection.new(configuration: conf)
9
+ client.connect(addr: HOST, now: Time.now.to_f)
10
+ s = UDPSocket.new
11
+ s.connect("0.0.0.0", 4433)
12
+
13
+ # handshake
14
+ 3.times do
15
+ client.datagrams_to_send(now: Time.now.to_f).each do |data, addr|
16
+ # pp data
17
+ s.send(data, 0)
18
+ end
19
+ data, addr = s.recvfrom(65536)
20
+ client.receive_datagram(data: data, addr: HOST, now: Time.now.to_f)
21
+ # puts("()()()()()()()()()(()()")
22
+ # pp client.events
23
+ while ev = client.next_event
24
+ pp ev
25
+ end
26
+ end
27
+
28
+ stream_id = client.get_next_available_stream_id
29
+
30
+ 2.times do
31
+ client.send_stream_data(stream_id: stream_id, data: "hello")
32
+ client.datagrams_to_send(now: Time.now.to_f).each do |data, addr|
33
+ # pp data
34
+ s.send(data, 0)
35
+ end
36
+ data, addr = s.recvfrom(65536)
37
+ client.receive_datagram(data: data, addr: HOST, now: Time.now.to_f)
38
+ while ev = client.next_event
39
+ pp ev
40
+ end
41
+ end
42
+ client.close
@@ -0,0 +1,43 @@
1
+ require "raioquic"
2
+ require "socket"
3
+
4
+ HOST = ["0.0.0.0", 4433]
5
+
6
+ conf = Raioquic::Quic::QuicConfiguration.new(is_client: false)
7
+ conf.load_cert_chain("localhost-unasuke-dev.crt", "key.pem")
8
+ server = Raioquic::Quic::Connection::QuicConnection.new(configuration: conf)
9
+ # client.connect(addr: HOST, now: Time.now.to_f)
10
+ s = UDPSocket.new
11
+ s.bi
12
+ s.connect("0.0.0.0", 4433)
13
+
14
+ # handshake
15
+ 3.times do
16
+ client.datagrams_to_send(now: Time.now.to_f).each do |data, addr|
17
+ # pp data
18
+ s.send(data, 0)
19
+ end
20
+ data, addr = s.recvfrom(65536)
21
+ client.receive_datagram(data: data, addr: HOST, now: Time.now.to_f)
22
+ # puts("()()()()()()()()()(()()")
23
+ # pp client.events
24
+ while ev = client.next_event
25
+ pp ev
26
+ end
27
+ end
28
+
29
+ stream_id = client.get_next_available_stream_id
30
+
31
+ 2.times do
32
+ client.send_stream_data(stream_id: stream_id, data: "hello")
33
+ client.datagrams_to_send(now: Time.now.to_f).each do |data, addr|
34
+ # pp data
35
+ s.send(data, 0)
36
+ end
37
+ data, addr = s.recvfrom(65536)
38
+ client.receive_datagram(data: data, addr: HOST, now: Time.now.to_f)
39
+ while ev = client.next_event
40
+ pp ev
41
+ end
42
+ end
43
+ client.close
@@ -0,0 +1,108 @@
1
+ require "raioquic"
2
+
3
+ # quiche
4
+ curl_example_1 = [
5
+ "c400000001101b631c6391a91309c29395aaa5c180bc14db7dafc0e1c10407ff4a9527a71103bb7eb59a91004120487b50905ab8b9b40a27ca5bcbf7" \
6
+ "72f2d4fc76d64f4b81e9c713e872df9d9dba3f65cf1ed5673b0b3da9ca484779da091e11c0627c0c1f46f211bb02ca838a44644594073abaa7001523" \
7
+ "94d9d27b8c996294be08843066e2a20724f9f565d18235af6d6a70cae0021118a36248abe019b261be18b41b55aeb5efa99d82d78c4cdf914447b86b" \
8
+ "dfa8c2e71869dd11ba0f07ef3a706e4210906ab7185331c8170e2e4221b1c5a8a7344dbb314d3431b16ec0097d6735cbf1aca08230b03df55f65ce90" \
9
+ "8f641c010d3d049c9bc1c54f7b5aca0715abba7dad2b44270e79b5f3a867da03e45ddf3c4193e513ed6c0d2006452b8a149cf6dc4f506312ad5bb664" \
10
+ "c51ed3339225a574b769ca32a4b927b00d3f6e8b672ebbf8255c08b9077ffa8cede10000000000000000000000000000000000000000000000000000" \
11
+ "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \
12
+ "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \
13
+ "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \
14
+ "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \
15
+ "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \
16
+ "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \
17
+ "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \
18
+ "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \
19
+ "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \
20
+ "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \
21
+ "00000000000000000000000000000000000000000000000000000000000000000000000000000000"
22
+ ].pack("H*")
23
+
24
+ # ngtcp2
25
+ curl_example_2 = [
26
+ "cf0000000114ffd7b7163e917c1564d3d466140e55786193ad2614bb91e0a62b38bc5636a092b3bedddd6c2fb3a634008000047c0f37f579dfb9d2f7" \
27
+ "72adec386b2fa74b17f869dfe69c7fdd43f573c1a7da1a1f8933b74fd4bee9857a9c7ae5c6bf901dee6e89cd36534605e415f7362af8cdeb8dce366b" \
28
+ "686a12c5926b67cd4eb51a971901acd26065adc289fc880ef571a6be517106b4289bc0e88c55ee359bde58d03b3d51c3f1bbdff3533992b392213266" \
29
+ "0693b769f62cbeef2e04894fa6b7a07754bad53e59c41e997193fa59a31d3d28bb2fe7f92fde06c95e95bd987b9f2931ce3d099e3a6d81063f27f0a9" \
30
+ "39f968dec45630822d16a4d21d2b64826f58accec67895dca92e288ab0cf8e499042bb8610d27ef93f06df0baa756c1f6f4f2d547f0225469c5dec8d" \
31
+ "f30f2e5d446a2fa46282493c736cb64b7e7915ee9b8da09c2923438af552a7486d97ec86ccabace868165e0a7537599e3bfacd3014e1c440713e3e45" \
32
+ "1fcd4a69aa5bd4e4bdaacc36c677467557a858563f6d1fcfdc2e90e393fb408a08fccb1dc5ab3d34380b8c19a866b7a2e02b6d4a790b8c1affc60441" \
33
+ "8f2f9910955a9a7bd9863fae2b88ca154205902c46d02c0cd77b94b60246a4c30b2981776c8d81f02a2ef99650e15c23393dfe2e9a331a82b9dadd0c" \
34
+ "a0ccde5eb1076679d341073d58fbb3ff0a92d7dc22175a8b005e412b3c46d0b9d46aa61ab9aa1adfcf91435d549986ef3c93c84ab24c5712de9bf459" \
35
+ "c5932506be63f66f8329c465d047aeea3d6f955222cad794b2260aa7bab3e58898261c5e4e120341cc06a770b6b5080160f9106d597637f3c70e1c2f" \
36
+ "bd11061ed5e222fa0fb2c1168b3e4c73379b1ffc483565faa70af74ee43f7021270d8e0539588bc81676a38020da2928f51eacad7fb7e701d5f733d9" \
37
+ "7b4d9268110ba3e32d3080a560346487f99c4250ef8632e8078170913ffa3074f3e2f68d807a465fda271580a76ba99b67580e2549c0e895fea9329d" \
38
+ "df6dbb8f2c23032f84ad84dd51f812c2666b7d073ee73abb642005419325d9cd22b5c18fac3ac5bc0031c6aad50006ac9ee9dddf8fcdf8f58c261a5b" \
39
+ "86432f3994e8455dbc7b2d8885db3fec3f1a8e1f880a17531896d3d8f7014c4dc13ae757836ecbd2e69c628bf2d539d7aae293e127cd565426e65a07" \
40
+ "5cd4f5ff42a7d6df2c1743fb68922e09103424d0752d92cce6a3301a899050874a4b1255bf1b24f66e047826cbc92c5532ae6c38fbede4d356d98cf0" \
41
+ "b61acd508c8e5b9d64b83b84870b62065e9101831bbdbd61bac77edd2971b371a8c39fd79c00c2c3fd9e8f2c0480815d115d34e57a27bf963bd55748" \
42
+ "491c86f4918b4b84c4ca497a7da6e04a8aa3610c4b02c9336c80a87e6198800bacead5308b2b3f1d371faefb54dc13386539d27ae1acd01c60427d9b" \
43
+ "3db437b91b8e89c804c28739f9f2f16a63ef2538673ff78ab13cbcb4fba64c45b8dfc54901b3bf09b0d0931b800c07782a044f27a370f11183d7c7ec" \
44
+ "429406bfa84d67dfbd1622331be2e8ee1787a1ac27b994812df08fd59e2ba6e70773978d6d4373d92e740d921338120772f26d720957d7797bbdd0c3" \
45
+ "e8b35bf634f2c7609d0fb369524e82105d13d3dd293c62272a81a1fafd91e06a9e89e13346fc0c0e550212fb38e773791e5ebf5cb02cbe757542c489"
46
+ ].pack("H*")
47
+
48
+ # msh3
49
+ curl_example_3 = [
50
+ "c90000000108653439dec7a03435000044b253edca3c42d9eb3096dc53f2199151ee39c14794fff635d4b38a88f8625434729196b2b0d7a07dc49a7e" \
51
+ "540c36d4915ca58752432c7c0ffc5cc6933ea6f21665d00d1975b43447173595a6b6e6ea33ea485146a996366c47a5db51ea8a2543123134560bc701" \
52
+ "866c501390acb349cbcd358632a2f0c22eb403458a1388f351d94994afbf66d3826a1abe0563aba293db1c1ef1c998a37f61bd76bc07fb7c84806efa" \
53
+ "fc83c5b83390cc97504524afb71d7a1f5001e2b28c9d8c1d56db8b7465ddb008956f62729586cd67133d01739f80abbceb0ddefde61a4721388cab0f" \
54
+ "e52e6a494f2c970d84ca4dfea36b2bbfa380d2f2c55fb4eacbfd5d9627a4b2a13bc6088f6185eca600df452648343d6491b8466026bb9230096198ae" \
55
+ "a12785a122bf7c0e2d975f6a023571bc2bddb30f170cf50bb8619ecd7fa5430f632d392d345e44165c9c193d7aaa1f5d21772d31f5fe2f799ec2b74b" \
56
+ "2b5cf01d0d436d16bb103e61ae3cf314c23e902fdb41d9bcc0f505b097dff110d6533c7818b4cdc06b9f7f0667e2fe46a9ee903a98ba3e4ef130e510" \
57
+ "c356fe4090ed4fa86e071875dcd92f4e4eeb059826f19eb7342b498b0eb2485b885312e38392cbb3168ea56668101c804c4ed7146cb4a951677abfb9" \
58
+ "26f9260eec03bcbab39867ab95149147b13f5ad7d0583045e09362f3e4f34711529cf5d691675ee646bd4b67c86f38b0972e593392fdea34b081956a" \
59
+ "f78e3af3abf03006c585e1a9753cc2022a7c7af89ebb2af272e428c02256d39999ddc1c68c64ac0460a45905d23b9d2ed1f6223631e86c022ad0d161" \
60
+ "071f876054dc965b8876ae1bb6cbb4526c85edf81f44cbb9151853e313365509a8b874086779e1238f06e528118668883a81b8b948c8f9ce4e867672" \
61
+ "888e79871b1fb204b821fd9b9a20f7f1ef471cf42b5afa7015edd94bdf0ed4a34326a52bc1b0afaa7f06a10a0cc81bc425abd57cdb2fdf9dce23c29a" \
62
+ "b9a899c66a99f218c9979de34b72b1c6f6d0cd1d3bd4327f23f49f4ece3e8c0eeeeb6792565a60f2ac458f84cdbffd9a68192709b03b9172f0701000" \
63
+ "13364ee8af8afebaf95b2d42140c482a7167e2b0b81c83db0d78c50952f02dc211c274946d7279a130cbf090690fa74b130d0985a3b0e420aea25256" \
64
+ "d20135793fedcc3d1b2e983751115d1f63867f60032b9c4d998d3826ec7168e234990f57e905a36c15e6161360f825d4448a6a7c7579a685a70f4070" \
65
+ "d23f1ccb8fa27c7c20f7f02fa04888d30f9cb1fdcaf2b8004ecd38e36b5eb780d96991602c2e31c038d716afb178cf70d856f092c11c7b486219c7e9" \
66
+ "89519badcc25051ff271d53735a6b39676669859255174ae2ba14417cda1a7657004f0225eb042e716c467ed5c9d6756d376aae59cc8c8eb109ce337" \
67
+ "61f23b608066087021360ef3f1793e7d1566482582672ddd8b2566b739e3f357dea4618853a60d6ccbc91ff4390caef222577aab2034e9eefa931ead" \
68
+ "1e53c9f147374c7fb5a80583c2dafc4a0f91ed9dfa331fade079d06ef06410e6e18a1b1d9f0dc29b1a13ed0c6fce86e9cba88d2b566b0f13bce9757e" \
69
+ "d9c35d5bb1c5e9984202d1be90ef3538df613cbd4dd7d0a4f608d6f3de2a4a68ad10a77ff08ec0ab7f5c3efc3f3cb5f24d63353059b9d69e0a47dc74" \
70
+ "d81edd95ef717fcc28f3c6e2f8ce321bf0aba3fa"
71
+ ].pack("H*")
72
+
73
+ [curl_example_1, curl_example_2, curl_example_3].each do |curl_example|
74
+ buf = Raioquic::Buffer.new(data: curl_example)
75
+ pp Raioquic::Quic::Packet.pull_quic_header(buf: buf, host_cid_length: 8)
76
+ puts "\n"
77
+ end
78
+
79
+ # $ bundle exec ruby example/parse_curl_example.rb
80
+ # #<Raioquic::Quic::Packet::QuicHeader:0x00007f844f836280
81
+ # @destination_cid="\ec\x1Cc\x91\xA9\x13\t\xC2\x93\x95\xAA\xA5\xC1\x80\xBC",
82
+ # @integrity_tag="",
83
+ # @is_long_header=true,
84
+ # @packet_type=192,
85
+ # @rest_length=288,
86
+ # @source_cid="\xDB}\xAF\xC0\xE1\xC1\x04\a\xFFJ\x95'\xA7\x11\x03\xBB~\xB5\x9A\x91",
87
+ # @token="",
88
+ # @version=1>
89
+ #
90
+ # #<Raioquic::Quic::Packet::QuicHeader:0x00007f844f82cc80
91
+ # @destination_cid="\xFF\xD7\xB7\x16>\x91|\x15d\xD3\xD4f\x14\x0EUxa\x93\xAD&",
92
+ # @integrity_tag="",
93
+ # @is_long_header=true,
94
+ # @packet_type=192,
95
+ # @rest_length=1148,
96
+ # @source_cid="\xBB\x91\xE0\xA6+8\xBCV6\xA0\x92\xB3\xBE\xDD\xDDl/\xB3\xA64",
97
+ # @token="",
98
+ # @version=1>
99
+ #
100
+ # #<Raioquic::Quic::Packet::QuicHeader:0x00007f844f82c0a0
101
+ # @destination_cid="e49\xDE\xC7\xA045",
102
+ # @integrity_tag="",
103
+ # @is_long_header=true,
104
+ # @packet_type=192,
105
+ # @rest_length=1202,
106
+ # @source_cid="",
107
+ # @token="",
108
+ # @version=1>
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stringio"
4
+ require "forwardable"
5
+
6
+ module Raioquic
7
+ # Raioquic::Buffer
8
+ # Migrated from auiquic/src/aioquic/buffer.py
9
+ class Buffer
10
+ extend Forwardable
11
+
12
+ BufferReadError = Class.new(StandardError)
13
+ BufferWriteError = Class.new(StandardError)
14
+
15
+ UINT_VAR_MAX = 0x3fffffffffffffff
16
+ UINT_VAR_MAX_SIZE = 8
17
+
18
+ def_delegator :@buffer, :eof, :eof
19
+ def_delegator :@buffer, :tell, :tell
20
+
21
+ attr_reader :capacity
22
+
23
+ # Encode a variable-length unsigned integer.
24
+ def self.encode_uint_var(value)
25
+ buf = new(capacity: UINT_VAR_MAX_SIZE)
26
+ buf.push_uint_var(value)
27
+ buf.data
28
+ end
29
+
30
+ # Return the number of bytes required to encode the given value
31
+ # as a QUIC variable-length unsigned integer.
32
+ def self.size_uint_var(value)
33
+ if value <= 0x3f # rubocop:disable Style/GuardClause
34
+ return 1
35
+ elsif value <= 0x3fff
36
+ return 2
37
+ elsif value <= 0x3fffffff
38
+ return 4
39
+ elsif value <= 0x3fffffffffffffff
40
+ return 8
41
+ else
42
+ raise ValueError, "Integer is too big for a variable-length integer"
43
+ end
44
+ end
45
+
46
+ def initialize(capacity: nil, data: "")
47
+ @position = 0 # bytes count
48
+ @buffer = StringIO.open((+data).force_encoding(Encoding::ASCII_8BIT), "rb+:ASCII-8bit:ASCII-8BIT")
49
+ @capacity = capacity || @buffer.size
50
+ end
51
+
52
+ def dealloc
53
+ # empty
54
+ end
55
+
56
+ def data
57
+ data_slice(start: 0, ends: tell)
58
+ end
59
+
60
+ def seek(offset)
61
+ check_read_bounds(offset)
62
+ @buffer.seek(offset)
63
+ rescue Errno::EINVAL
64
+ raise BufferReadError
65
+ end
66
+
67
+ # def capacity
68
+ # @capacity
69
+ # end
70
+
71
+ # NOTE: "end" is reserved keyword in Ruby
72
+ def data_slice(start:, ends:)
73
+ orig_str = @buffer.string
74
+
75
+ raise BufferReadError, "Read out of bounds" if start < 0 || @buffer.size < start || ends < 0 || @buffer.size < ends || ends < start
76
+
77
+ orig_str.byteslice(start, ends - start)
78
+ end
79
+
80
+ # Pull bytes.
81
+ def pull_bytes(length)
82
+ raise BufferReadError if @buffer.size < 1
83
+
84
+ @buffer.read(length)
85
+ rescue EOFError, ArgumentError
86
+ raise BufferReadError
87
+ end
88
+
89
+ # Pull an 8-bit unsigned integer.
90
+ def pull_uint8
91
+ @buffer.readpartial(1).unpack1("C")
92
+ rescue EOFError
93
+ raise BufferReadError
94
+ end
95
+
96
+ # Pull a 16-bit unsigned integer.
97
+ def pull_uint16
98
+ @buffer.readpartial(2).unpack1("n")
99
+ rescue EOFError
100
+ raise BufferReadError
101
+ end
102
+
103
+ # Pull a 32-bit unsigned integer.
104
+ def pull_uint32
105
+ @buffer.readpartial(4).unpack1("N")
106
+ rescue EOFError
107
+ raise BufferReadError
108
+ end
109
+
110
+ # Pull a 64-bit unsigned integer.
111
+ def pull_uint64
112
+ @buffer.readpartial(8).unpack1("Q>")
113
+ rescue EOFError
114
+ raise BufferReadError
115
+ end
116
+
117
+ # Pull a QUIC variable-length unsigned integer.
118
+ def pull_uint_var
119
+ check_read_bounds(1)
120
+ first = pull_uint8
121
+ case first >> 6
122
+ when 0
123
+ first & 0x3f
124
+ when 1
125
+ check_read_bounds(1)
126
+ second = pull_uint8
127
+ ((first & 0x3f) << 8) | (second)
128
+ when 2
129
+ check_read_bounds(3)
130
+ second = pull_uint8
131
+ third = pull_uint8
132
+ forth = pull_uint8
133
+ ((first & 0x3f) << 24) | (second << 16) | (third << 8) | forth
134
+ else
135
+ check_read_bounds(7)
136
+ b2 = pull_uint8
137
+ b3 = pull_uint8
138
+ b4 = pull_uint8
139
+ b5 = pull_uint8
140
+ b6 = pull_uint8
141
+ b7 = pull_uint8
142
+ b8 = pull_uint8
143
+ ((first & 0x3f) << 56) | (b2 << 48) | (b3 << 40) | (b4 << 32) | (b5 << 24) | (b6 << 16) | (b7 << 8) | b8
144
+ end
145
+ end
146
+
147
+ def push_bytes(value)
148
+ raise BufferWriteError if value.bytesize > [@buffer.size, @capacity].max
149
+
150
+ @buffer << value
151
+ end
152
+
153
+ def push_uint8(value)
154
+ @buffer << [value].pack("C")
155
+ end
156
+
157
+ def push_uint16(value)
158
+ @buffer << [value].pack("n")
159
+ end
160
+
161
+ def push_uint32(value)
162
+ @buffer << [value].pack("N")
163
+ end
164
+
165
+ def push_uint64(value)
166
+ @buffer << [value].pack("Q>")
167
+ end
168
+
169
+ def push_uint_var(value)
170
+ if value <= 0x3f
171
+ check_read_bounds(1)
172
+ push_uint8(value)
173
+ elsif value <= 0x3fff
174
+ check_read_bounds(2)
175
+ push_uint8((value >> 8) | 0x40)
176
+ push_uint8(value & 0xff)
177
+ elsif value <= 0x3fffffff
178
+ check_read_bounds(4)
179
+ push_uint8((value >> 24) | 0x80)
180
+ push_uint8((value >> 16) & 0xff)
181
+ push_uint8((value >> 8) & 0xff)
182
+ push_uint8(value)
183
+ elsif value <= 0x3fffffffffffffff
184
+ check_read_bounds(8)
185
+ push_uint8((value >> 56) | 0xc0)
186
+ push_uint8((value >> 48) & 0xff)
187
+ push_uint8((value >> 40) & 0xff)
188
+ push_uint8((value >> 32) & 0xff)
189
+ push_uint8((value >> 24) & 0xff)
190
+ push_uint8((value >> 16) & 0xff)
191
+ push_uint8((value >> 8) & 0xff)
192
+ push_uint8(value & 0xff)
193
+ else
194
+ raise ValueError, "Integer is too big for a variable-length integer"
195
+ end
196
+ end
197
+
198
+ private def check_read_bounds(length)
199
+ raise BufferReadError, "Read out of bounds" if [@buffer.size, @capacity].max < length
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ # CoreExt (like active support)
4
+
5
+ # CoreExt for String class
6
+ class String
7
+ # Convert big endian bytes to integer (unsigned, up to 4 byte)
8
+ def bytes_to_int
9
+ case bytesize
10
+ when 0
11
+ 0
12
+ when 1
13
+ ("\x00" + self).unpack1("n*").to_i # rubocop:disable Style/StringConcatenation
14
+ when 2
15
+ unpack1("n*").to_i
16
+ when 3
17
+ ("\x00" + self).unpack1("N*").to_i # rubocop:disable Style/StringConcatenation
18
+ when 4
19
+ unpack1("N*").to_i
20
+ else
21
+ raise ArgumentError, "#{bytesize} bytes is not supported"
22
+ end
23
+ end
24
+ end
25
+
26
+ # CoreExt for Integer class
27
+ class Integer
28
+ # Convert integer to specific byte size string (unsigned, big endian)
29
+ def to_bytes(bytesize) # rubocop:disable Metrics/CyclomaticComplexity
30
+ raise ArgumentError if self > 4294967295 # 5 bytes
31
+
32
+ template = case bytesize
33
+ when 0
34
+ nil
35
+ when 1
36
+ "C*"
37
+ when 2
38
+ "n*"
39
+ when 3..4
40
+ "N*"
41
+ else
42
+ raise ArgumentError, "#{bytesize} bytes is too big"
43
+ end
44
+ return "" if template.nil?
45
+
46
+ [self].pack(template).then do |str|
47
+ if bytesize == 3
48
+ str.byteslice(1, 3).to_s
49
+ else
50
+ str
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,5 @@
1
+ # Raioquic::Crypto
2
+
3
+ The codes under the directory heavily inspired by `pyca/cryptography`
4
+
5
+ <https://github.com/pyca/cryptography>
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "backend/aead"
4
+
5
+ module Raioquic
6
+ module Crypto
7
+ # Raioquic::Crypto::AESGCM
8
+ # Migrated from pyca/cryptography/src/cryptography/hazmat/primitives/ciphers/aead.py
9
+ class AESGCM
10
+ attr_reader :key
11
+
12
+ MAX_SIZE = (2**31) - 1
13
+ OverflowError = Class.new(StandardError)
14
+
15
+ def initialize(key)
16
+ raise TypeError unless key.is_a? String
17
+ raise ValueError, "AESGCM key must be 128, 192, or 256 bits." unless [16, 24, 32].include?(key.length)
18
+
19
+ @key = key
20
+ end
21
+
22
+ def self.generate_key(bit_length)
23
+ raise TypeError, "bit_length must be an integer" unless bit_length.is_a? Integer
24
+ raise ValueError, "bit_length must be 128, 192, or 256" unless [128, 192, 256].include?(bit_length)
25
+
26
+ Random.urandom(bit_length / 8)
27
+ end
28
+
29
+ def encrypt(nonce:, data:, associated_data: "")
30
+ raise OverflowError, "Data or associated data too long. Max 2**31 - 1 bytes" if data.length > MAX_SIZE || associated_data.length > MAX_SIZE
31
+
32
+ check_nonce(nonce)
33
+ validate_length(nonce: nonce, data: data)
34
+
35
+ Backend::Aead.encrypt(cipher: self, key: @key, nonce: nonce, data: data, associated_data: [associated_data], tag_length: 16)
36
+ end
37
+
38
+ def decrypt(nonce:, data:, associated_data: "")
39
+ Backend::Aead.decrypt(cipher: self, key: @key, nonce: nonce, data: data, associated_data: [associated_data], tag_length: 16)
40
+ end
41
+
42
+ private def validate_length(nonce:, data:)
43
+ l_val = 15 - nonce.length
44
+ raise ValueError, "Data too long for nonce" if 2 * (8 * l_val) < data.length
45
+ end
46
+
47
+ private def check_nonce(nonce)
48
+ raise ValueError, "Nonce must be 12 bytes" if nonce.length != 12
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+
5
+ module Raioquic
6
+ module Crypto
7
+ module Backend
8
+ # Raioquic's Crypto::Backend is only OpenSSL
9
+ # Migrated from pyca/cryptography/src/cryptography/hazmat/backends/openssl/aead.py
10
+ class Aead
11
+ def self.aead_cipher_name(cipher)
12
+ case cipher
13
+ # when ChaCha20Poly1305
14
+ # when AESCCM
15
+ # "aes-#{cipher.key.length}-ccm"
16
+ when ::Raioquic::Crypto::AESGCM
17
+ "aes-#{cipher.key.length * 8}-gcm"
18
+ else
19
+ raise RuntimeError
20
+ end
21
+ end
22
+ private_class_method :aead_cipher_name
23
+
24
+ def self.encrypt(cipher:, key:, nonce:, data:, associated_data: [], tag_length:)
25
+ cipher_name = aead_cipher_name(cipher)
26
+ cipher = OpenSSL::Cipher.new(cipher_name)
27
+ cipher.encrypt
28
+ cipher.key = key
29
+ cipher.iv = nonce
30
+ cipher.auth_data = associated_data.join
31
+ encrypted = cipher.update(data) + cipher.final
32
+ tag = cipher.auth_tag(tag_length)
33
+ return encrypted + tag
34
+ end
35
+
36
+ def self.decrypt(cipher:, key:, nonce:, data:, associated_data: [], tag_length:)
37
+ cipher_name = aead_cipher_name(cipher)
38
+ cipher = OpenSSL::Cipher.new(cipher_name)
39
+ tag = data.slice(-tag_length, tag_length)
40
+ encryped = data.slice(0, data.length - tag_length)
41
+ cipher.decrypt
42
+ cipher.key = key
43
+ cipher.iv = nonce
44
+ cipher.auth_tag = tag
45
+ cipher.auth_data = associated_data.join
46
+ decrypted = cipher.update(encryped) + cipher.final
47
+ return decrypted
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "aesgcm"
4
+
5
+ module Raioquic
6
+ module Crypto
7
+ # Raioquic::Crypto::Backend
8
+ # Migrated from pyca/cryptography/src/cryptography/hazmat/backends/openssl/backend.py
9
+ module Backend
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "crypto/backend"
4
+
5
+ module Raioquic
6
+ # Raioquic::Crypto
7
+ # Cryptography modules migrated from pyca/cryptography
8
+ module Crypto
9
+ end
10
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+
5
+ module Raioquic
6
+ module Quic
7
+ # Raioquic::Quic::Configuration
8
+ # Migrated from aioquic/src/aioquic/quic/configuration.py
9
+ # A QUIC configration
10
+ class QuicConfiguration
11
+ attr_accessor :alpn_protocols
12
+ attr_accessor :connection_id_length
13
+ attr_accessor :idle_timeout
14
+ attr_accessor :is_client
15
+ attr_accessor :max_data
16
+ attr_accessor :max_stream_data
17
+ attr_accessor :quic_logger
18
+ attr_accessor :secrets_log_file
19
+ attr_accessor :server_name
20
+ attr_accessor :session_ticket
21
+ attr_accessor :cadata
22
+ attr_accessor :cafile
23
+ attr_accessor :capath
24
+ attr_accessor :certificate
25
+ attr_accessor :certificate_chain
26
+ attr_accessor :cipher_suites
27
+ attr_accessor :initial_rtt
28
+ attr_accessor :max_datagram_frame_size
29
+ attr_accessor :private_key
30
+ attr_accessor :quantam_readiness_test
31
+ attr_accessor :supported_versions
32
+ attr_accessor :verify_mode
33
+
34
+ def initialize(**kwargs) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
35
+ @alpn_protocols = kwargs[:alpn_protocols]
36
+ @connection_id_length = kwargs[:connection_id_length] || 8
37
+ @idle_timeout = kwargs[:idle_timeout] || 60.0
38
+ @is_client = kwargs[:is_client]
39
+ @max_data = kwargs[:max_data] || 1048576
40
+ @max_stream_data = kwargs[:max_stream_data] || 1048576
41
+ @quic_logger = kwargs[:quic_logger]
42
+ @secrets_log_file = kwargs[:secrets_log_file]
43
+ @server_name = kwargs[:server_name]
44
+ @session_ticket = kwargs[:session_ticket]
45
+ @cadata = kwargs[:cadata]
46
+ @cafile = kwargs[:cafile]
47
+ @capath = kwargs[:capath]
48
+ @certificate = kwargs[:certificate]
49
+ @certificate_chain = kwargs[:certificate_chain]
50
+ @cipher_suites = kwargs[:cipher_suites]
51
+ @initial_rtt = kwargs[:initial_rtt] || 0.1
52
+ @max_datagram_frame_size = kwargs[:max_datagram_frame_size]
53
+ @private_key = kwargs[:private_key]
54
+ @quantam_readiness_test = kwargs[:quantam_readiness_test] || false
55
+ @supported_versions = kwargs[:supported_versions] || [Packet::QuicProtocolVersion::VERSION_1]
56
+ @verify_mode = kwargs[:verify_mode]
57
+ end
58
+
59
+ # Load a private key and the corresponding certificate.
60
+ def load_cert_chain(certfile, keyfile = nil, password = nil)
61
+ boundary = "-----BEGIN PRIVATE KEY-----\n"
62
+ cert_body = File.read(certfile)
63
+ certs = OpenSSL::X509::Certificate.load(cert_body)
64
+
65
+ @certificate = certs[0]
66
+ @certificate_chain = certs[1..] if certs.length > 1
67
+
68
+ @private_key = OpenSSL::PKey.read(cert_body) if cert_body.include?(boundary)
69
+
70
+ @private_key = OpenSSL::PKey.read(File.read(keyfile), password) if keyfile
71
+ end
72
+
73
+ # Load a set of "certification authority" (CA) certificates used to validate other peer's certificates.
74
+ def load_verify_locations(cafile: nil, capath: nil, cadata: nil)
75
+ @cafile = cafile
76
+ @capath = capath
77
+ @cadata = cadata
78
+ end
79
+ end
80
+ end
81
+ end