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