thrift 0.22.0 → 0.24.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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +235 -17
  3. data/benchmark/benchmark.rb +23 -8
  4. data/benchmark/client.rb +50 -6
  5. data/benchmark/server.rb +46 -7
  6. data/benchmark/thin_server.rb +2 -0
  7. data/ext/binary_protocol_accelerated.c +154 -42
  8. data/ext/bytes.c +14 -0
  9. data/ext/compact_protocol.c +138 -45
  10. data/ext/constants.h +12 -0
  11. data/ext/extconf.rb +17 -8
  12. data/ext/memory_buffer.c +44 -7
  13. data/ext/protocol.c +29 -0
  14. data/ext/protocol.h +35 -0
  15. data/ext/struct.c +48 -17
  16. data/ext/thrift_native.c +28 -3
  17. data/lib/thrift/bytes.rb +70 -100
  18. data/lib/thrift/client.rb +54 -13
  19. data/lib/thrift/exceptions.rb +6 -5
  20. data/lib/thrift/multiplexed_processor.rb +20 -10
  21. data/lib/thrift/processor.rb +7 -6
  22. data/lib/thrift/protocol/base_protocol.rb +52 -21
  23. data/lib/thrift/protocol/binary_protocol.rb +65 -20
  24. data/lib/thrift/protocol/binary_protocol_accelerated.rb +6 -5
  25. data/lib/thrift/protocol/compact_protocol.rb +76 -41
  26. data/lib/thrift/protocol/header_protocol.rb +321 -0
  27. data/lib/thrift/protocol/json_protocol.rb +44 -27
  28. data/lib/thrift/protocol/multiplexed_protocol.rb +6 -5
  29. data/lib/thrift/protocol/protocol_decorator.rb +13 -4
  30. data/lib/thrift/serializer/deserializer.rb +6 -5
  31. data/lib/thrift/serializer/serializer.rb +5 -5
  32. data/lib/thrift/server/base_server.rb +5 -4
  33. data/lib/thrift/server/mongrel_http_server.rb +7 -6
  34. data/lib/thrift/server/nonblocking_server.rb +35 -9
  35. data/lib/thrift/server/simple_server.rb +13 -5
  36. data/lib/thrift/server/thin_http_server.rb +4 -3
  37. data/lib/thrift/server/thread_pool_server.rb +7 -6
  38. data/lib/thrift/server/threaded_server.rb +13 -5
  39. data/lib/thrift/struct.rb +12 -11
  40. data/lib/thrift/struct_union.rb +14 -9
  41. data/lib/thrift/thrift_native.rb +3 -2
  42. data/lib/thrift/transport/base_server_transport.rb +8 -5
  43. data/lib/thrift/transport/base_transport.rb +18 -14
  44. data/lib/thrift/transport/buffered_transport.rb +7 -6
  45. data/lib/thrift/transport/framed_transport.rb +8 -7
  46. data/lib/thrift/transport/header_transport.rb +562 -0
  47. data/lib/thrift/transport/http_client_transport.rb +2 -1
  48. data/lib/thrift/transport/io_stream_transport.rb +4 -3
  49. data/lib/thrift/transport/memory_buffer_transport.rb +13 -6
  50. data/lib/thrift/transport/server_socket.rb +14 -8
  51. data/lib/thrift/transport/socket.rb +126 -60
  52. data/lib/thrift/transport/ssl_server_socket.rb +4 -3
  53. data/lib/thrift/transport/ssl_socket.rb +45 -13
  54. data/lib/thrift/transport/unix_server_socket.rb +9 -5
  55. data/lib/thrift/transport/unix_socket.rb +7 -6
  56. data/lib/thrift/types.rb +10 -6
  57. data/lib/thrift/union.rb +15 -8
  58. data/lib/thrift/uuid.rb +50 -0
  59. data/lib/thrift.rb +4 -1
  60. data/spec/ThriftSpec.thrift +21 -1
  61. data/spec/base_protocol_spec.rb +21 -2
  62. data/spec/base_transport_spec.rb +48 -8
  63. data/spec/binary_protocol_accelerated_spec.rb +1 -0
  64. data/spec/binary_protocol_spec.rb +1 -2
  65. data/spec/binary_protocol_spec_shared.rb +206 -155
  66. data/spec/bytes_spec.rb +71 -114
  67. data/spec/client_spec.rb +86 -19
  68. data/spec/compact_protocol_spec.rb +153 -16
  69. data/spec/constants_demo_spec.rb +102 -0
  70. data/spec/exception_spec.rb +1 -1
  71. data/spec/flat_spec.rb +1 -0
  72. data/spec/header_protocol_spec.rb +476 -0
  73. data/spec/header_transport_spec.rb +431 -0
  74. data/spec/http_client_spec.rb +5 -6
  75. data/spec/json_protocol_spec.rb +69 -47
  76. data/spec/multiplexed_processor_spec.rb +75 -0
  77. data/spec/namespaced_spec.rb +1 -1
  78. data/spec/nonblocking_server_spec.rb +174 -8
  79. data/spec/processor_spec.rb +1 -1
  80. data/spec/recursion_depth_spec.rb +223 -0
  81. data/spec/serializer_spec.rb +1 -1
  82. data/spec/server_socket_spec.rb +38 -1
  83. data/spec/server_spec.rb +60 -9
  84. data/spec/socket_spec.rb +119 -13
  85. data/spec/socket_spec_shared.rb +73 -9
  86. data/spec/spec_helper.rb +2 -1
  87. data/spec/ssl_server_socket_spec.rb +52 -1
  88. data/spec/ssl_socket_spec.rb +181 -11
  89. data/spec/struct_nested_containers_spec.rb +2 -2
  90. data/spec/struct_spec.rb +114 -9
  91. data/spec/support/header_protocol_helper.rb +55 -0
  92. data/spec/thin_http_server_spec.rb +4 -18
  93. data/spec/types_spec.rb +26 -26
  94. data/spec/union_spec.rb +70 -11
  95. data/spec/unix_socket_spec.rb +17 -2
  96. data/spec/uuid_validation_spec.rb +239 -0
  97. data/test/fuzz/Makefile +779 -0
  98. data/test/fuzz/Makefile.am +173 -0
  99. data/test/fuzz/Makefile.in +775 -0
  100. data/test/fuzz/README.md +149 -0
  101. data/test/fuzz/fuzz_common.rb +96 -0
  102. data/{lib/thrift/core_ext.rb → test/fuzz/fuzz_parse_binary_protocol.rb} +4 -4
  103. data/{lib/thrift/core_ext/fixnum.rb → test/fuzz/fuzz_parse_binary_protocol_accelerated.rb} +7 -13
  104. data/test/fuzz/fuzz_parse_binary_protocol_accelerated_harness.rb +23 -0
  105. data/test/fuzz/fuzz_parse_binary_protocol_harness.rb +23 -0
  106. data/test/fuzz/fuzz_parse_compact_protocol.rb +23 -0
  107. data/test/fuzz/fuzz_parse_compact_protocol_harness.rb +23 -0
  108. data/test/fuzz/fuzz_parse_json_protocol.rb +23 -0
  109. data/test/fuzz/fuzz_parse_json_protocol_harness.rb +23 -0
  110. data/test/fuzz/fuzz_roundtrip_binary_protocol.rb +23 -0
  111. data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated.rb +23 -0
  112. data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated_harness.rb +23 -0
  113. data/test/fuzz/fuzz_roundtrip_binary_protocol_harness.rb +23 -0
  114. data/test/fuzz/fuzz_roundtrip_compact_protocol.rb +23 -0
  115. data/test/fuzz/fuzz_roundtrip_compact_protocol_harness.rb +23 -0
  116. data/test/fuzz/fuzz_roundtrip_json_protocol.rb +23 -0
  117. data/test/fuzz/fuzz_roundtrip_json_protocol_harness.rb +23 -0
  118. data/test/fuzz/fuzz_tracer.rb +29 -0
  119. metadata +105 -70
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  #
2
3
  # Licensed to the Apache Software Foundation (ASF) under one
3
4
  # or more contributor license agreements. See the NOTICE file
@@ -67,38 +68,101 @@ shared_examples_for "a socket" do
67
68
  @socket.open
68
69
  allow(@handle).to receive(:closed?).and_return(true)
69
70
  expect(@socket).not_to be_open
70
- expect { @socket.write("fail") }.to raise_error(IOError, "closed stream")
71
- expect { @socket.read(10) }.to raise_error(IOError, "closed stream")
71
+ expect { @socket.write("fail") }.to raise_error(Thrift::TransportException, "closed stream") { |e| expect(e.type).to eq(Thrift::TransportException::NOT_OPEN) }
72
+ expect { @socket.read(10) }.to raise_error(Thrift::TransportException, "closed stream") { |e| expect(e.type).to eq(Thrift::TransportException::NOT_OPEN) }
72
73
  end
73
74
 
74
75
  it "should support the timeout accessor for read" do
75
76
  @socket.timeout = 3
76
77
  @socket.open
77
- expect(IO).to receive(:select).with([@handle], nil, nil, 3).and_return([[@handle], [], []])
78
- expect(@handle).to receive(:readpartial).with(17).and_return("test data")
78
+ expect(@handle).to receive(:read_nonblock).with(17).and_raise(IO::EAGAINWaitReadable)
79
+ expect(IO).to receive(:select) do |rd, wr, err, timeout|
80
+ expect(rd).to eq([@handle])
81
+ expect(wr).to be_nil
82
+ expect(err).to be_nil
83
+ expect(timeout).to be > 0
84
+ expect(timeout).to be <= 3
85
+ [[@handle], [], []]
86
+ end
87
+ expect(@handle).to receive(:read_nonblock).with(17).and_return("test data")
79
88
  expect(@socket.read(17)).to eq("test data")
80
89
  end
81
90
 
82
91
  it "should support the timeout accessor for write" do
83
92
  @socket.timeout = 3
84
93
  @socket.open
85
- expect(IO).to receive(:select).with(nil, [@handle], nil, 3).twice.and_return([[], [@handle], []])
86
- expect(@handle).to receive(:write_nonblock).with("test data").and_return(4)
87
- expect(@handle).to receive(:write_nonblock).with(" data").and_return(5)
94
+ write_calls = 0
95
+ expect(@handle).to receive(:write_nonblock).exactly(3).times do |chunk|
96
+ write_calls += 1
97
+ case write_calls
98
+ when 1
99
+ expect(chunk).to eq("test data")
100
+ raise IO::EAGAINWaitWritable
101
+ when 2
102
+ expect(chunk).to eq("test data")
103
+ 4
104
+ when 3
105
+ expect(chunk).to eq(" data")
106
+ 5
107
+ end
108
+ end
109
+ expect(IO).to receive(:select) do |rd, wr, err, timeout|
110
+ expect(rd).to be_nil
111
+ expect(wr).to eq([@handle])
112
+ expect(err).to be_nil
113
+ expect(timeout).to be > 0
114
+ expect(timeout).to be <= 3
115
+ [[], [@handle], []]
116
+ end
88
117
  expect(@socket.write("test data")).to eq(9)
89
118
  end
90
119
 
91
120
  it "should raise an error when read times out" do
92
121
  @socket.timeout = 0.5
93
122
  @socket.open
94
- expect(IO).to receive(:select).once {sleep(0.5); nil}
123
+ expect(@handle).to receive(:read_nonblock).with(17).and_raise(IO::EAGAINWaitReadable)
124
+ expect(IO).to receive(:select).once { sleep(0.6); nil }
95
125
  expect { @socket.read(17) }.to raise_error(Thrift::TransportException) { |e| expect(e.type).to eq(Thrift::TransportException::TIMED_OUT) }
96
126
  end
97
127
 
98
128
  it "should raise an error when write times out" do
99
129
  @socket.timeout = 0.5
100
130
  @socket.open
101
- allow(IO).to receive(:select).with(nil, [@handle], nil, 0.5).and_return(nil)
131
+ expect(@handle).to receive(:write_nonblock).with("test data").and_raise(IO::EAGAINWaitWritable)
132
+ expect(IO).to receive(:select).once { sleep(0.6); nil }
102
133
  expect { @socket.write("test data") }.to raise_error(Thrift::TransportException) { |e| expect(e.type).to eq(Thrift::TransportException::TIMED_OUT) }
103
134
  end
135
+
136
+ it "should read buffered SSL data without waiting on the raw socket again" do
137
+ @socket.timeout = 1
138
+ @socket.open
139
+
140
+ expect(@handle).to receive(:read_nonblock).with(4).ordered.and_raise(IO::EAGAINWaitReadable)
141
+ expect(IO).to receive(:select).once.ordered do |rd, wr, err, timeout|
142
+ expect(rd).to eq([@handle])
143
+ expect(wr).to be_nil
144
+ expect(err).to be_nil
145
+ expect(timeout).to be > 0
146
+ expect(timeout).to be <= 1
147
+ [[@handle], [], []]
148
+ end
149
+ expect(@handle).to receive(:read_nonblock).with(4).ordered.and_return("ABCD")
150
+ expect(@handle).to receive(:read_nonblock).with(5).ordered.and_return("12345")
151
+
152
+ expect(@socket.read(4)).to eq("ABCD")
153
+ expect(@socket.read(5)).to eq("12345")
154
+ end
155
+
156
+ it "should read without timeout using the blocking path" do
157
+ @socket.timeout = nil
158
+ @socket.open
159
+
160
+ expect(IO).not_to receive(:select)
161
+ expect(@handle).not_to receive(:read_nonblock)
162
+ expect(@handle).to receive(:readpartial).with(4).ordered.and_return("ABCD")
163
+ expect(@handle).to receive(:readpartial).with(5).ordered.and_return("12345")
164
+
165
+ expect(@socket.read(4)).to eq("ABCD")
166
+ expect(@socket.read(5)).to eq("12345")
167
+ end
104
168
  end
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # encoding: UTF-8
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # Licensed to the Apache Software Foundation (ASF) under one
4
5
  # or more contributor license agreements. See the NOTICE file
@@ -55,7 +56,7 @@ require 'nonblocking_service'
55
56
 
56
57
  module Fixtures
57
58
  COMPACT_PROTOCOL_TEST_STRUCT = Thrift::Test::COMPACT_TEST.dup
58
- COMPACT_PROTOCOL_TEST_STRUCT.a_binary = [0,1,2,3,4,5,6,7,8].pack('c*')
59
+ COMPACT_PROTOCOL_TEST_STRUCT.a_binary = [0, 1, 2, 3, 4, 5, 6, 7, 8].pack('c*')
59
60
  COMPACT_PROTOCOL_TEST_STRUCT.set_byte_map = nil
60
61
  COMPACT_PROTOCOL_TEST_STRUCT.map_byte_map = nil
61
62
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  #
2
3
  # Licensed to the Apache Software Foundation (ASF) under one
3
4
  # or more contributor license agreements. See the NOTICE file
@@ -21,12 +22,62 @@ require 'spec_helper'
21
22
  require File.expand_path("#{File.dirname(__FILE__)}/socket_spec_shared")
22
23
 
23
24
  describe 'SSLServerSocket' do
24
-
25
25
  describe Thrift::SSLServerSocket do
26
26
  before(:each) do
27
27
  @socket = Thrift::SSLServerSocket.new(1234)
28
28
  end
29
29
 
30
+ it "should delegate to_io to the underlying SSL server handle" do
31
+ tcp_server = double("TCPServer")
32
+ ssl_server = double("SSLServer")
33
+
34
+ allow(TCPServer).to receive(:new).with(nil, 1234).and_return(tcp_server)
35
+ allow(OpenSSL::SSL::SSLServer).to receive(:new).with(tcp_server, nil).and_return(ssl_server)
36
+ allow(ssl_server).to receive(:to_io).and_return(tcp_server)
37
+
38
+ @socket.listen
39
+ expect(@socket.to_io).to eq(tcp_server)
40
+ end
41
+
42
+ it "should default accepted sockets to a finite client timeout" do
43
+ expect(@socket.client_timeout).to eq(5)
44
+ end
45
+
46
+ it "should preserve the positional ssl context argument" do
47
+ context = double("SSLContext")
48
+ socket = Thrift::SSLServerSocket.new(nil, 1234, context)
49
+
50
+ expect(socket.ssl_context).to eq(context)
51
+ expect(socket.client_timeout).to eq(5)
52
+ end
53
+
54
+ it "should accept a custom client timeout" do
55
+ context = double("SSLContext")
56
+ socket = Thrift::SSLServerSocket.new(nil, 1234, context, client_timeout: 2.5)
57
+
58
+ expect(socket.ssl_context).to eq(context)
59
+ expect(socket.client_timeout).to eq(2.5)
60
+ end
61
+
62
+ it "should apply the client timeout to accepted sockets" do
63
+ tcp_server = double("TCPServer")
64
+ ssl_server = double("SSLServer")
65
+ sock = double("SSLSocket")
66
+
67
+ allow(TCPServer).to receive(:new).with(nil, 1234).and_return(tcp_server)
68
+ allow(OpenSSL::SSL::SSLServer).to receive(:new).with(tcp_server, nil).and_return(ssl_server)
69
+ expect(ssl_server).to receive(:accept).and_return(sock)
70
+ expect(sock).to receive(:setsockopt).with(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
71
+
72
+ trans = double("Socket")
73
+ expect(Thrift::Socket).to receive(:new).and_return(trans)
74
+ expect(trans).to receive(:timeout=).with(Thrift::BaseServerTransport::DEFAULT_CLIENT_TIMEOUT)
75
+ expect(trans).to receive(:handle=).with(sock)
76
+
77
+ @socket.listen
78
+ expect(@socket.accept).to eq(trans)
79
+ end
80
+
30
81
  it "should provide a reasonable to_s" do
31
82
  expect(@socket.to_s).to eq("ssl(socket(:1234))")
32
83
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  #
2
3
  # Licensed to the Apache Software Foundation (ASF) under one
3
4
  # or more contributor license agreements. See the NOTICE file
@@ -21,44 +22,60 @@ require 'spec_helper'
21
22
  require File.expand_path("#{File.dirname(__FILE__)}/socket_spec_shared")
22
23
 
23
24
  describe 'SSLSocket' do
24
-
25
25
  describe Thrift::SSLSocket do
26
26
  before(:each) do
27
27
  @context = OpenSSL::SSL::SSLContext.new
28
28
  @socket = Thrift::SSLSocket.new
29
+ @addrinfo = double("Addrinfo")
29
30
  @simple_socket_handle = double("Handle", :closed? => false)
30
31
  allow(@simple_socket_handle).to receive(:close)
31
- allow(@simple_socket_handle).to receive(:connect_nonblock)
32
32
  allow(@simple_socket_handle).to receive(:setsockopt)
33
+ allow(@simple_socket_handle).to receive(:wait_readable)
34
+ allow(@simple_socket_handle).to receive(:wait_writable)
33
35
 
34
- @handle = double(double("SSLHandle", :connect_nonblock => true, :post_connection_check => true), :closed? => false)
35
- allow(@handle).to receive(:connect_nonblock)
36
+ @handle = double("SSLHandle", :closed? => false)
37
+ allow(@handle).to receive(:connect).and_return(true)
38
+ allow(@handle).to receive(:connect_nonblock).and_return(@handle)
36
39
  allow(@handle).to receive(:close)
37
40
  allow(@handle).to receive(:post_connection_check)
41
+ allow(@handle).to receive(:sync_close=)
42
+ allow(@handle).to receive(:to_io).and_return(@simple_socket_handle)
38
43
 
39
- allow(::Socket).to receive(:new).and_return(@simple_socket_handle)
44
+ allow(@addrinfo).to receive(:connect).and_return(@simple_socket_handle)
45
+ allow(Addrinfo).to receive(:foreach).and_yield(@addrinfo)
40
46
  allow(OpenSSL::SSL::SSLSocket).to receive(:new).and_return(@handle)
41
47
  end
42
48
 
43
49
  it_should_behave_like "a socket"
44
50
 
45
51
  it "should raise a TransportException when it cannot open a ssl socket" do
46
- expect(::Socket).to receive(:getaddrinfo).with("localhost", 9090, nil, ::Socket::SOCK_STREAM).and_return([[]])
47
- expect { @socket.open }.to raise_error(Thrift::TransportException) { |e| expect(e.type).to eq(Thrift::TransportException::NOT_OPEN) }
52
+ allow(Addrinfo).to receive(:foreach).and_raise(SocketError.new("lookup failed"))
53
+ expect { @socket.open }.to raise_error(Thrift::TransportException) do |e|
54
+ expect(e.type).to eq(Thrift::TransportException::NOT_OPEN)
55
+ expect(e.message).to eq("Could not connect to localhost:9090")
56
+ expect(e.cause).to be_a(SocketError)
57
+ end
48
58
  end
49
59
 
50
60
  it "should open a ::Socket with default args" do
61
+ expect(@simple_socket_handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
51
62
  expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(@simple_socket_handle, nil).and_return(@handle)
63
+ expect(@handle).to receive(:sync_close=).with(true)
64
+ expect(@handle).to receive(:connect).and_return(true)
52
65
  expect(@handle).to receive(:post_connection_check).with('localhost')
53
66
  @socket.open
54
67
  end
55
68
 
56
69
  it "should accept host/port options" do
57
- handle = double("Handle", :connect_nonblock => true, :setsockopt => nil)
58
- allow(::Socket).to receive(:new).and_return(handle)
59
- expect(::Socket).to receive(:getaddrinfo).with("my.domain", 1234, nil, ::Socket::SOCK_STREAM).and_return([[]])
60
- expect(::Socket).to receive(:sockaddr_in)
70
+ handle = double("Handle", :closed? => false)
71
+ allow(handle).to receive(:close)
72
+ expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 100.0)
73
+ expect(handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
74
+ expect(Addrinfo).to receive(:foreach).with("my.domain", 1234, nil, :STREAM).and_yield(@addrinfo)
75
+ expect(@addrinfo).to receive(:connect).with(timeout: 6000).and_return(handle)
61
76
  expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(handle, nil).and_return(@handle)
77
+ expect(@handle).to receive(:sync_close=).with(true)
78
+ expect(@handle).to receive(:connect_nonblock).with(exception: false).and_return(@handle)
62
79
  expect(@handle).to receive(:post_connection_check).with('my.domain')
63
80
  Thrift::SSLSocket.new('my.domain', 1234, 6000, nil).open
64
81
  end
@@ -71,6 +88,159 @@ describe 'SSLSocket' do
71
88
  expect(Thrift::SSLSocket.new('localhost', 8080, 5, @context).ssl_context).to eq(@context)
72
89
  end
73
90
 
91
+ it "should treat zero timeout as blocking for open and handshake" do
92
+ @socket.timeout = 0
93
+
94
+ expect(@addrinfo).to receive(:connect).with(no_args).and_return(@simple_socket_handle)
95
+ expect(@simple_socket_handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
96
+ expect(@handle).to receive(:sync_close=).with(true)
97
+ expect(@handle).to receive(:connect).and_return(true)
98
+ expect(@handle).not_to receive(:connect_nonblock)
99
+ expect(@handle).to receive(:post_connection_check).with('localhost')
100
+
101
+ expect(@socket.open).to eq(@handle)
102
+ end
103
+
104
+ it "should use the remaining timeout across ssl wait states" do
105
+ @socket.timeout = 5
106
+
107
+ expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 101.0, 102.0, 104.0)
108
+ expect(@addrinfo).to receive(:connect).with(timeout: 4.0).and_return(@simple_socket_handle)
109
+ expect(@simple_socket_handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
110
+ expect(@handle).to receive(:sync_close=).with(true)
111
+ expect(@handle).to receive(:connect_nonblock).with(exception: false).ordered.and_return(:wait_readable)
112
+ expect(@simple_socket_handle).to receive(:wait_readable).with(3.0).ordered.and_return(true)
113
+ expect(@handle).to receive(:connect_nonblock).with(exception: false).ordered.and_return(:wait_writable)
114
+ expect(@simple_socket_handle).to receive(:wait_writable).with(1.0).ordered.and_return(true)
115
+ expect(@handle).to receive(:connect_nonblock).with(exception: false).ordered.and_return(@handle)
116
+ expect(@handle).to receive(:post_connection_check).with('localhost')
117
+
118
+ expect(@socket.open).to eq(@handle)
119
+ end
120
+
121
+ it "should share one timeout budget across tcp fallback and the ssl handshake" do
122
+ @socket.timeout = 5
123
+ second_addrinfo = double("Addrinfo")
124
+
125
+ expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 101.0, 102.0, 103.0)
126
+ expect(Addrinfo).to receive(:foreach).with("localhost", 9090, nil, :STREAM).and_yield(@addrinfo).and_yield(second_addrinfo)
127
+ expect(@addrinfo).to receive(:connect).with(timeout: 4.0).and_raise(Errno::ECONNREFUSED)
128
+ expect(second_addrinfo).to receive(:connect).with(timeout: 3.0).and_return(@simple_socket_handle)
129
+ expect(@simple_socket_handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
130
+ expect(@handle).to receive(:sync_close=).with(true)
131
+ expect(@handle).to receive(:connect_nonblock).with(exception: false).ordered.and_return(:wait_readable)
132
+ expect(@simple_socket_handle).to receive(:wait_readable).with(2.0).ordered.and_return(true)
133
+ expect(@handle).to receive(:connect_nonblock).with(exception: false).ordered.and_return(@handle)
134
+ expect(@handle).to receive(:post_connection_check).with('localhost')
135
+
136
+ expect(@socket.open).to eq(@handle)
137
+ end
138
+
139
+ it "should continue to the next address after a tcp connect timeout during ssl open" do
140
+ @socket.timeout = 5
141
+ second_addrinfo = double("Addrinfo")
142
+
143
+ expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 101.0, 103.0)
144
+ expect(Addrinfo).to receive(:foreach).with("localhost", 9090, nil, :STREAM).and_yield(@addrinfo).and_yield(second_addrinfo)
145
+ expect(@addrinfo).to receive(:connect).with(timeout: 4.0).and_raise(Errno::ETIMEDOUT)
146
+ expect(second_addrinfo).to receive(:connect).with(timeout: 2.0).and_return(@simple_socket_handle)
147
+ expect(@simple_socket_handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
148
+ expect(@handle).to receive(:sync_close=).with(true)
149
+ expect(@handle).to receive(:connect_nonblock).with(exception: false).and_return(@handle)
150
+ expect(@handle).to receive(:post_connection_check).with('localhost')
151
+
152
+ expect(@socket.open).to eq(@handle)
153
+ end
154
+
155
+ it "should raise TIMED_OUT when ssl handshake wait times out" do
156
+ @socket.timeout = 5
157
+
158
+ expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 101.0, 102.0)
159
+ expect(@addrinfo).to receive(:connect).with(timeout: 4.0).and_return(@simple_socket_handle)
160
+ expect(@simple_socket_handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
161
+ expect(@handle).to receive(:sync_close=).with(true)
162
+ expect(@handle).to receive(:connect_nonblock).with(exception: false).and_return(:wait_readable)
163
+ expect(@simple_socket_handle).to receive(:wait_readable).with(3.0).and_return(nil)
164
+ expect(@handle).to receive(:close)
165
+
166
+ expect { @socket.open }.to raise_error(Thrift::TransportException) do |e|
167
+ expect(e.type).to eq(Thrift::TransportException::TIMED_OUT)
168
+ expect(e.message).to eq("SSL socket: Timed out establishing session with localhost:9090")
169
+ end
170
+ end
171
+
172
+ it "should surface tcp open timeout before starting ssl" do
173
+ @socket.timeout = 5
174
+
175
+ expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 101.0)
176
+ expect(@addrinfo).to receive(:connect).with(timeout: 4.0).and_raise(Errno::ETIMEDOUT)
177
+ expect(OpenSSL::SSL::SSLSocket).not_to receive(:new)
178
+
179
+ expect { @socket.open }.to raise_error(Thrift::TransportException) do |e|
180
+ expect(e.type).to eq(Thrift::TransportException::TIMED_OUT)
181
+ expect(e.message).to eq("Socket: Timed out opening connection to localhost:9090")
182
+ end
183
+ end
184
+
185
+ it "should close the raw socket if ssl wrapper creation fails" do
186
+ @socket.timeout = 5
187
+
188
+ expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 101.0)
189
+ expect(@addrinfo).to receive(:connect).with(timeout: 4.0).and_return(@simple_socket_handle)
190
+ expect(@simple_socket_handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
191
+ expect(OpenSSL::SSL::SSLSocket).to receive(:new).with(@simple_socket_handle, nil).and_raise(StandardError.new("ssl init failed"))
192
+ expect(@simple_socket_handle).to receive(:close)
193
+
194
+ expect { @socket.open }.to raise_error(Thrift::TransportException) do |e|
195
+ expect(e.type).to eq(Thrift::TransportException::NOT_OPEN)
196
+ expect(e.message).to eq("Could not connect to localhost:9090")
197
+ expect(e.cause.message).to eq("ssl init failed")
198
+ end
199
+ end
200
+
201
+ it "should close the ssl socket when post connection check fails" do
202
+ @socket.timeout = 5
203
+
204
+ expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 101.0)
205
+ expect(@addrinfo).to receive(:connect).with(timeout: 4.0).and_return(@simple_socket_handle)
206
+ expect(@simple_socket_handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
207
+ expect(@handle).to receive(:sync_close=).with(true)
208
+ expect(@handle).to receive(:connect_nonblock).with(exception: false).and_return(@handle)
209
+ expect(@handle).to receive(:post_connection_check).with('localhost').and_raise(StandardError.new("hostname mismatch"))
210
+ expect(@handle).to receive(:close)
211
+
212
+ expect { @socket.open }.to raise_error(Thrift::TransportException) do |e|
213
+ expect(e.type).to eq(Thrift::TransportException::NOT_OPEN)
214
+ expect(e.message).to eq("Could not connect to localhost:9090")
215
+ expect(e.cause.message).to eq("hostname mismatch")
216
+ end
217
+ end
218
+
219
+ it "should close the ssl socket on an unexpected nonblocking handshake result" do
220
+ @socket.timeout = 5
221
+
222
+ expect(Process).to receive(:clock_gettime).with(Process::CLOCK_MONOTONIC).and_return(100.0, 101.0)
223
+ expect(@addrinfo).to receive(:connect).with(timeout: 4.0).and_return(@simple_socket_handle)
224
+ expect(@simple_socket_handle).to receive(:setsockopt).with(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, 1)
225
+ expect(@handle).to receive(:sync_close=).with(true)
226
+ expect(@handle).to receive(:connect_nonblock).with(exception: false).and_return(:unexpected)
227
+ expect(@handle).to receive(:close)
228
+
229
+ expect { @socket.open }.to raise_error(Thrift::TransportException) do |e|
230
+ expect(e.type).to eq(Thrift::TransportException::NOT_OPEN)
231
+ expect(e.message).to include("unexpected SSL connect result")
232
+ end
233
+ end
234
+
235
+ it "should delegate to_io to the underlying SSL socket handle" do
236
+ @socket.open
237
+ expect(@socket.to_io).to eq(@simple_socket_handle)
238
+ end
239
+
240
+ it "should raise IOError when to_io is called on a closed stream" do
241
+ expect { @socket.to_io }.to raise_error(IOError, 'closed stream')
242
+ end
243
+
74
244
  it "should provide a reasonable to_s" do
75
245
  expect(Thrift::SSLSocket.new('myhost', 8090).to_s).to eq("ssl(socket(myhost:8090))")
76
246
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  #
2
3
  # Licensed to the Apache Software Foundation (ASF) under one
3
4
  # or more contributor license agreements. See the NOTICE file
@@ -20,7 +21,6 @@
20
21
  require 'spec_helper'
21
22
 
22
23
  describe 'StructNestedContainers' do
23
-
24
24
  def with_type_checking
25
25
  saved_type_checking, Thrift.type_checking = Thrift.type_checking, true
26
26
  begin
@@ -166,7 +166,7 @@ describe 'StructNestedContainers' do
166
166
  with_type_checking do
167
167
  a, b = SpecNamespace::NestedMapInMapKey.new, SpecNamespace::NestedMapInMapKey.new
168
168
  [a, b].each do |thrift_struct|
169
- thrift_struct.value = { { 1 => 2, 3 => 4} => 1, {2 => 3, 4 => 5} => 2 }
169
+ thrift_struct.value = { { 1 => 2, 3 => 4} => 1, {2 => 3, 4 => 5} => 2 }
170
170
  thrift_struct.validate
171
171
  end
172
172
  expect(a).to eq(b)
data/spec/struct_spec.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  #
2
3
  # Licensed to the Apache Software Foundation (ASF) under one
3
4
  # or more contributor license agreements. See the NOTICE file
@@ -20,11 +21,10 @@
20
21
  require 'spec_helper'
21
22
 
22
23
  describe 'Struct' do
23
-
24
24
  describe Thrift::Struct do
25
25
  it "should iterate over all fields properly" do
26
26
  fields = {}
27
- SpecNamespace::Foo.new.each_field { |fid,field_info| fields[fid] = field_info }
27
+ SpecNamespace::Foo.new.each_field { |fid, field_info| fields[fid] = field_info }
28
28
  expect(fields).to eq(SpecNamespace::Foo::FIELDS)
29
29
  end
30
30
 
@@ -51,10 +51,10 @@ describe 'Struct' do
51
51
  begin
52
52
  struct = SpecNamespace::Foo.new
53
53
  struct.ints << 17
54
- expect(SpecNamespace::Foo.new.ints).to eq([1,2,2,3])
54
+ expect(SpecNamespace::Foo.new.ints).to eq([1, 2, 2, 3])
55
55
  ensure
56
56
  # ensure no leakage to other tests
57
- SpecNamespace::Foo::FIELDS[4][:default] = [1,2,2,3]
57
+ SpecNamespace::Foo::FIELDS[4][:default] = [1, 2, 2, 3]
58
58
  end
59
59
  end
60
60
 
@@ -142,6 +142,34 @@ describe 'Struct' do
142
142
  expect(struct.shorts).to eq(Set.new([3, 2]))
143
143
  end
144
144
 
145
+ it "rejects negative container sizes while reading" do
146
+ struct = SpecNamespace::Foo.new
147
+ prot = Thrift::BaseProtocol.new(double("transport"))
148
+
149
+ expect(prot).to receive(:read_list_begin).and_return([Thrift::Types::I32, -1])
150
+
151
+ expect {
152
+ struct.send(:read_field, prot, SpecNamespace::Foo::FIELDS[4])
153
+ }.to raise_error(Thrift::ProtocolException, "Negative size") { |error|
154
+ expect(error.type).to eq(Thrift::ProtocolException::NEGATIVE_SIZE)
155
+ }
156
+ end
157
+
158
+ it "does not preallocate arrays from declared list sizes" do
159
+ struct = SpecNamespace::Foo.new
160
+ prot = Thrift::BaseProtocol.new(double("transport"))
161
+ declared_size = 1 << 30
162
+ sentinel = RuntimeError.new("stop after first element")
163
+
164
+ expect(prot).to receive(:read_list_begin).and_return([Thrift::Types::I32, declared_size])
165
+ expect(prot).to receive(:read_i32).and_raise(sentinel)
166
+ expect(Array).not_to receive(:new).with(declared_size)
167
+
168
+ expect {
169
+ struct.send(:read_field, prot, SpecNamespace::Foo::FIELDS[4])
170
+ }.to raise_error(sentinel)
171
+ end
172
+
145
173
  it "should serialize false boolean fields correctly" do
146
174
  b = SpecNamespace::BoolStruct.new(:yesno => false)
147
175
  prot = Thrift::BinaryProtocol.new(Thrift::MemoryBufferTransport.new)
@@ -181,7 +209,7 @@ describe 'Struct' do
181
209
  end
182
210
 
183
211
  it "should write itself to the wire" do
184
- prot = Thrift::BaseProtocol.new(double("transport")) #mock("Protocol")
212
+ prot = Thrift::BaseProtocol.new(double("transport")) # mock("Protocol")
185
213
  expect(prot).to receive(:write_struct_begin).with("SpecNamespace::Foo")
186
214
  expect(prot).to receive(:write_struct_begin).with("SpecNamespace::Hello")
187
215
  expect(prot).to receive(:write_struct_end).twice
@@ -227,7 +255,7 @@ describe 'Struct' do
227
255
  it "should support optional type-checking in Thrift::Struct.new" do
228
256
  Thrift.type_checking = true
229
257
  begin
230
- expect { SpecNamespace::Hello.new(:greeting => 3) }.to raise_error(Thrift::TypeError, /Expected Types::STRING, received (Integer|Fixnum) for field greeting/)
258
+ expect { SpecNamespace::Hello.new(:greeting => 3) }.to raise_error(Thrift::TypeError, "Expected Types::STRING, received Integer for field greeting")
231
259
  ensure
232
260
  Thrift.type_checking = false
233
261
  end
@@ -238,7 +266,7 @@ describe 'Struct' do
238
266
  Thrift.type_checking = true
239
267
  begin
240
268
  hello = SpecNamespace::Hello.new
241
- expect { hello.greeting = 3 }.to raise_error(Thrift::TypeError, /Expected Types::STRING, received (Integer|Fixnum) for field greeting/)
269
+ expect { hello.greeting = 3 }.to raise_error(Thrift::TypeError, "Expected Types::STRING, received Integer for field greeting")
242
270
  ensure
243
271
  Thrift.type_checking = false
244
272
  end
@@ -259,9 +287,9 @@ describe 'Struct' do
259
287
  prot = Thrift::BaseProtocol.new(double("trans"))
260
288
  expect(prot).to receive(:write_struct_begin).with("SpecNamespace::Xception")
261
289
  expect(prot).to receive(:write_struct_end)
262
- expect(prot).to receive(:write_field_begin).with('message', Thrift::Types::STRING, 1)#, "something happened")
290
+ expect(prot).to receive(:write_field_begin).with('message', Thrift::Types::STRING, 1)
263
291
  expect(prot).to receive(:write_string).with("something happened")
264
- expect(prot).to receive(:write_field_begin).with('code', Thrift::Types::I32, 2)#, 1)
292
+ expect(prot).to receive(:write_field_begin).with('code', Thrift::Types::I32, 2)
265
293
  expect(prot).to receive(:write_i32).with(1)
266
294
  expect(prot).to receive(:write_field_stop)
267
295
  expect(prot).to receive(:write_field_end).twice
@@ -289,5 +317,82 @@ describe 'Struct' do
289
317
  e.write(prot)
290
318
  end
291
319
  end
320
+
321
+ it "should handle UUID fields in structs" do
322
+ struct = SpecNamespace::Foo.new(
323
+ simple: 42,
324
+ words: 'test',
325
+ opt_uuid: '550e8400-e29b-41d4-a716-446655440000'
326
+ )
327
+
328
+ trans = Thrift::MemoryBufferTransport.new
329
+ prot = Thrift::BinaryProtocol.new(trans)
330
+
331
+ struct.write(prot)
332
+
333
+ result = SpecNamespace::Foo.new
334
+ result.read(prot)
335
+
336
+ expect(result.simple).to eq(42)
337
+ expect(result.words).to eq('test')
338
+ expect(result.opt_uuid).to eq('550e8400-e29b-41d4-a716-446655440000')
339
+ end
340
+
341
+ it "should handle optional UUID fields when unset" do
342
+ struct = SpecNamespace::Foo.new(simple: 42, words: 'test')
343
+ expect(struct.opt_uuid).to be_nil
344
+ expect(struct.opt_uuid?).to be_falsey
345
+ end
346
+
347
+ it "should handle list of UUIDs in SimpleList" do
348
+ uuids = ['550e8400-e29b-41d4-a716-446655440000', '6ba7b810-9dad-11d1-80b4-00c04fd430c8']
349
+ struct = SpecNamespace::SimpleList.new(uuids: uuids)
350
+
351
+ trans = Thrift::MemoryBufferTransport.new
352
+ prot = Thrift::CompactProtocol.new(trans)
353
+
354
+ struct.write(prot)
355
+
356
+ result = SpecNamespace::SimpleList.new
357
+ result.read(prot)
358
+
359
+ expect(result.uuids).to eq(uuids)
360
+ end
361
+
362
+ it "should normalize UUID case to lowercase" do
363
+ struct = SpecNamespace::Foo.new(opt_uuid: '550E8400-E29B-41D4-A716-446655440000')
364
+
365
+ trans = Thrift::MemoryBufferTransport.new
366
+ prot = Thrift::BinaryProtocol.new(trans)
367
+
368
+ struct.write(prot)
369
+
370
+ result = SpecNamespace::Foo.new
371
+ result.read(prot)
372
+
373
+ expect(result.opt_uuid).to eq('550e8400-e29b-41d4-a716-446655440000')
374
+ end
375
+
376
+ it "should handle UUID alongside other types in SimpleList" do
377
+ struct = SpecNamespace::SimpleList.new(
378
+ bools: [true, false],
379
+ i32s: [1, 2, 3],
380
+ strings: ['hello', 'world'],
381
+ uuids: ['550e8400-e29b-41d4-a716-446655440000', '00000000-0000-0000-0000-000000000000']
382
+ )
383
+
384
+ trans = Thrift::MemoryBufferTransport.new
385
+ prot = Thrift::BinaryProtocol.new(trans)
386
+
387
+ struct.write(prot)
388
+
389
+ result = SpecNamespace::SimpleList.new
390
+ result.read(prot)
391
+
392
+ expect(result.bools).to eq([true, false])
393
+ expect(result.i32s).to eq([1, 2, 3])
394
+ expect(result.strings).to eq(['hello', 'world'])
395
+ expect(result.uuids).to eq(['550e8400-e29b-41d4-a716-446655440000', '00000000-0000-0000-0000-000000000000'])
396
+ end
292
397
  end
293
398
  end