thrift 0.22.0 → 0.23.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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +175 -17
  3. data/benchmark/benchmark.rb +22 -8
  4. data/benchmark/client.rb +49 -6
  5. data/benchmark/server.rb +45 -7
  6. data/benchmark/thin_server.rb +1 -0
  7. data/ext/binary_protocol_accelerated.c +76 -19
  8. data/ext/compact_protocol.c +80 -15
  9. data/ext/constants.h +12 -0
  10. data/ext/extconf.rb +10 -9
  11. data/ext/memory_buffer.c +7 -7
  12. data/ext/protocol.c +29 -0
  13. data/ext/protocol.h +35 -0
  14. data/ext/struct.c +36 -5
  15. data/ext/thrift_native.c +27 -3
  16. data/lib/thrift/bytes.rb +68 -101
  17. data/lib/thrift/client.rb +61 -9
  18. data/lib/thrift/exceptions.rb +5 -5
  19. data/lib/thrift/multiplexed_processor.rb +6 -6
  20. data/lib/thrift/processor.rb +6 -6
  21. data/lib/thrift/protocol/base_protocol.rb +37 -15
  22. data/lib/thrift/protocol/binary_protocol.rb +25 -9
  23. data/lib/thrift/protocol/binary_protocol_accelerated.rb +5 -5
  24. data/lib/thrift/protocol/compact_protocol.rb +61 -37
  25. data/lib/thrift/protocol/header_protocol.rb +320 -0
  26. data/lib/thrift/protocol/json_protocol.rb +26 -16
  27. data/lib/thrift/protocol/multiplexed_protocol.rb +5 -5
  28. data/lib/thrift/protocol/protocol_decorator.rb +12 -4
  29. data/lib/thrift/serializer/deserializer.rb +5 -5
  30. data/lib/thrift/serializer/serializer.rb +4 -5
  31. data/lib/thrift/server/base_server.rb +4 -4
  32. data/lib/thrift/server/mongrel_http_server.rb +6 -6
  33. data/lib/thrift/server/nonblocking_server.rb +8 -8
  34. data/lib/thrift/server/simple_server.rb +4 -4
  35. data/lib/thrift/server/thin_http_server.rb +3 -3
  36. data/lib/thrift/server/thread_pool_server.rb +6 -6
  37. data/lib/thrift/server/threaded_server.rb +4 -4
  38. data/lib/thrift/struct.rb +11 -11
  39. data/lib/thrift/struct_union.rb +19 -9
  40. data/lib/thrift/thrift_native.rb +1 -1
  41. data/lib/thrift/transport/base_server_transport.rb +5 -5
  42. data/lib/thrift/transport/base_transport.rb +12 -12
  43. data/lib/thrift/transport/buffered_transport.rb +6 -6
  44. data/lib/thrift/transport/framed_transport.rb +7 -7
  45. data/lib/thrift/transport/header_transport.rb +516 -0
  46. data/lib/thrift/transport/http_client_transport.rb +1 -1
  47. data/lib/thrift/transport/io_stream_transport.rb +3 -3
  48. data/lib/thrift/transport/memory_buffer_transport.rb +6 -6
  49. data/lib/thrift/transport/server_socket.rb +8 -5
  50. data/lib/thrift/transport/socket.rb +58 -31
  51. data/lib/thrift/transport/ssl_server_socket.rb +1 -1
  52. data/lib/thrift/transport/ssl_socket.rb +2 -2
  53. data/lib/thrift/transport/unix_server_socket.rb +4 -4
  54. data/lib/thrift/transport/unix_socket.rb +6 -6
  55. data/lib/thrift/types.rb +9 -6
  56. data/lib/thrift/union.rb +14 -8
  57. data/lib/thrift/uuid.rb +49 -0
  58. data/lib/thrift.rb +3 -1
  59. data/spec/ThriftSpec.thrift +5 -1
  60. data/spec/base_protocol_spec.rb +1 -2
  61. data/spec/base_transport_spec.rb +6 -7
  62. data/spec/binary_protocol_spec.rb +0 -2
  63. data/spec/binary_protocol_spec_shared.rb +129 -142
  64. data/spec/bytes_spec.rb +57 -118
  65. data/spec/client_spec.rb +85 -19
  66. data/spec/compact_protocol_spec.rb +54 -16
  67. data/spec/constants_demo_spec.rb +101 -0
  68. data/spec/exception_spec.rb +0 -1
  69. data/spec/header_protocol_spec.rb +475 -0
  70. data/spec/header_transport_spec.rb +386 -0
  71. data/spec/http_client_spec.rb +4 -6
  72. data/spec/json_protocol_spec.rb +47 -47
  73. data/spec/namespaced_spec.rb +0 -1
  74. data/spec/nonblocking_server_spec.rb +102 -4
  75. data/spec/processor_spec.rb +0 -1
  76. data/spec/serializer_spec.rb +0 -1
  77. data/spec/server_socket_spec.rb +1 -1
  78. data/spec/server_spec.rb +8 -9
  79. data/spec/socket_spec.rb +0 -1
  80. data/spec/socket_spec_shared.rb +72 -9
  81. data/spec/spec_helper.rb +1 -1
  82. data/spec/ssl_server_socket_spec.rb +12 -1
  83. data/spec/ssl_socket_spec.rb +10 -1
  84. data/spec/struct_nested_containers_spec.rb +1 -2
  85. data/spec/struct_spec.rb +113 -9
  86. data/spec/support/header_protocol_helper.rb +54 -0
  87. data/spec/thin_http_server_spec.rb +3 -18
  88. data/spec/types_spec.rb +25 -26
  89. data/spec/union_spec.rb +69 -11
  90. data/spec/unix_socket_spec.rb +1 -2
  91. data/spec/uuid_validation_spec.rb +238 -0
  92. data/test/fuzz/Makefile.am +173 -0
  93. data/test/fuzz/README.md +149 -0
  94. data/test/fuzz/fuzz_common.rb +95 -0
  95. data/{lib/thrift/core_ext.rb → test/fuzz/fuzz_parse_binary_protocol.rb} +3 -4
  96. data/{lib/thrift/core_ext/fixnum.rb → test/fuzz/fuzz_parse_binary_protocol_accelerated.rb} +6 -13
  97. data/test/fuzz/fuzz_parse_binary_protocol_accelerated_harness.rb +22 -0
  98. data/test/fuzz/fuzz_parse_binary_protocol_harness.rb +22 -0
  99. data/test/fuzz/fuzz_parse_compact_protocol.rb +22 -0
  100. data/test/fuzz/fuzz_parse_compact_protocol_harness.rb +22 -0
  101. data/test/fuzz/fuzz_parse_json_protocol.rb +22 -0
  102. data/test/fuzz/fuzz_parse_json_protocol_harness.rb +22 -0
  103. data/test/fuzz/fuzz_roundtrip_binary_protocol.rb +22 -0
  104. data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated.rb +22 -0
  105. data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated_harness.rb +22 -0
  106. data/test/fuzz/fuzz_roundtrip_binary_protocol_harness.rb +22 -0
  107. data/test/fuzz/fuzz_roundtrip_compact_protocol.rb +22 -0
  108. data/test/fuzz/fuzz_roundtrip_compact_protocol_harness.rb +22 -0
  109. data/test/fuzz/fuzz_roundtrip_json_protocol.rb +22 -0
  110. data/test/fuzz/fuzz_roundtrip_json_protocol_harness.rb +22 -0
  111. data/test/fuzz/fuzz_tracer.rb +28 -0
  112. metadata +106 -37
@@ -18,9 +18,9 @@
18
18
  #
19
19
 
20
20
  require 'spec_helper'
21
+ require 'timeout'
21
22
 
22
23
  describe 'NonblockingServer' do
23
-
24
24
  class Handler
25
25
  def initialize
26
26
  @queue = Queue.new
@@ -76,7 +76,7 @@ describe 'NonblockingServer' do
76
76
  @transport.read(sz)
77
77
  end
78
78
 
79
- def write(buf,sz=nil)
79
+ def write(buf, sz = nil)
80
80
  @transport.write(buf, sz)
81
81
  end
82
82
 
@@ -166,7 +166,7 @@ describe 'NonblockingServer' do
166
166
  break
167
167
  end
168
168
  end
169
- @clients.each { |c,t| t.close and break if c == client } #close the transport
169
+ @clients.each { |c, t| t.close and break if c == client } # close the transport
170
170
  rescue => e
171
171
  raise e unless @catch_exceptions
172
172
  end
@@ -245,7 +245,7 @@ describe 'NonblockingServer' do
245
245
  it "should kill active messages when they don't expire while shutting down" do
246
246
  result = Queue.new
247
247
  client = setup_client_thread(result)
248
- client << [:sleep, 10]
248
+ client << [:sleep, 10.0]
249
249
  sleep 0.1 # start processing the client's message
250
250
  @server.shutdown(1)
251
251
  @catch_exceptions = true
@@ -260,4 +260,102 @@ describe 'NonblockingServer' do
260
260
  expect(@server_thread.join(2)).not_to be_nil
261
261
  end
262
262
  end
263
+
264
+ describe "#{Thrift::NonblockingServer} with TLS transport" do
265
+ before(:each) do
266
+ @port = available_port
267
+ handler = Handler.new
268
+ processor = SpecNamespace::NonblockingService::Processor.new(handler)
269
+ @transport = Thrift::SSLServerSocket.new('localhost', @port, create_server_ssl_context)
270
+ transport_factory = Thrift::FramedTransportFactory.new
271
+ logger = Logger.new(STDERR)
272
+ logger.level = Logger::WARN
273
+ @server = Thrift::NonblockingServer.new(processor, @transport, transport_factory, nil, 5, logger)
274
+ handler.server = @server
275
+
276
+ @server_thread = Thread.new(Thread.current) do |master_thread|
277
+ begin
278
+ @server.serve
279
+ rescue => e
280
+ master_thread.raise e
281
+ end
282
+ end
283
+
284
+ @clients = []
285
+ wait_until_listening
286
+ end
287
+
288
+ after(:each) do
289
+ @clients.each(&:close)
290
+ @server.shutdown if @server
291
+ @server_thread.join(2) if @server_thread
292
+ @transport.close if @transport
293
+ end
294
+
295
+ it "should handle requests over TLS" do
296
+ expect(@server_thread).to be_alive
297
+
298
+ client = setup_tls_client
299
+ expect(client.greeting(true)).to eq(SpecNamespace::Hello.new)
300
+
301
+ @server.shutdown
302
+ expect(@server_thread.join(2)).to be_an_instance_of(Thread)
303
+ end
304
+
305
+ def setup_tls_client
306
+ transport = Thrift::FramedTransport.new(
307
+ Thrift::SSLSocket.new('localhost', @port, nil, create_client_ssl_context)
308
+ )
309
+ protocol = Thrift::BinaryProtocol.new(transport)
310
+ client = SpecNamespace::NonblockingService::Client.new(protocol)
311
+ transport.open
312
+ @clients << transport
313
+ client
314
+ end
315
+
316
+ def wait_until_listening
317
+ Timeout.timeout(2) do
318
+ until @transport.handle
319
+ raise "Server thread exited unexpectedly" unless @server_thread.alive?
320
+ sleep 0.01
321
+ end
322
+ end
323
+ end
324
+
325
+ def available_port
326
+ TCPServer.open('localhost', 0) { |server| server.addr[1] }
327
+ end
328
+
329
+ def ssl_keys_dir
330
+ File.expand_path('../../../test/keys', __dir__)
331
+ end
332
+
333
+ def create_server_ssl_context
334
+ OpenSSL::SSL::SSLContext.new.tap do |ctx|
335
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
336
+ if ctx.respond_to?(:min_version=) && OpenSSL::SSL.const_defined?(:TLS1_2_VERSION)
337
+ ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION
338
+ end
339
+ ctx.ca_file = File.join(ssl_keys_dir, 'CA.pem')
340
+ ctx.cert = OpenSSL::X509::Certificate.new(File.read(File.join(ssl_keys_dir, 'server.crt')))
341
+ ctx.cert_store = OpenSSL::X509::Store.new
342
+ ctx.cert_store.add_file(File.join(ssl_keys_dir, 'client.pem'))
343
+ ctx.key = OpenSSL::PKey::RSA.new(File.read(File.join(ssl_keys_dir, 'server.key')))
344
+ end
345
+ end
346
+
347
+ def create_client_ssl_context
348
+ OpenSSL::SSL::SSLContext.new.tap do |ctx|
349
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
350
+ if ctx.respond_to?(:min_version=) && OpenSSL::SSL.const_defined?(:TLS1_2_VERSION)
351
+ ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION
352
+ end
353
+ ctx.ca_file = File.join(ssl_keys_dir, 'CA.pem')
354
+ ctx.cert = OpenSSL::X509::Certificate.new(File.read(File.join(ssl_keys_dir, 'client.crt')))
355
+ ctx.cert_store = OpenSSL::X509::Store.new
356
+ ctx.cert_store.add_file(File.join(ssl_keys_dir, 'server.pem'))
357
+ ctx.key = OpenSSL::PKey::RSA.new(File.read(File.join(ssl_keys_dir, 'client.key')))
358
+ end
359
+ end
360
+ end
263
361
  end
@@ -20,7 +20,6 @@
20
20
  require 'spec_helper'
21
21
 
22
22
  describe 'Processor' do
23
-
24
23
  class ProcessorSpec
25
24
  include Thrift::Processor
26
25
  end
@@ -20,7 +20,6 @@
20
20
  require 'spec_helper'
21
21
 
22
22
  describe 'Serializer' do
23
-
24
23
  describe Thrift::Serializer do
25
24
  it "should serialize structs to binary by default" do
26
25
  serializer = Thrift::Serializer.new(Thrift::BinaryProtocolAcceleratedFactory.new)
@@ -21,7 +21,6 @@ require 'spec_helper'
21
21
  require File.expand_path("#{File.dirname(__FILE__)}/socket_spec_shared")
22
22
 
23
23
  describe 'Thrift::ServerSocket' do
24
-
25
24
  describe Thrift::ServerSocket do
26
25
  before(:each) do
27
26
  @socket = Thrift::ServerSocket.new(1234)
@@ -44,6 +43,7 @@ describe 'Thrift::ServerSocket' do
44
43
  expect(TCPServer).to receive(:new).with(nil, 1234).and_return(handle)
45
44
  @socket.listen
46
45
  sock = double("sock")
46
+ expect(sock).to receive(:setsockopt).with(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
47
47
  expect(handle).to receive(:accept).and_return(sock)
48
48
  trans = double("Socket")
49
49
  expect(Thrift::Socket).to receive(:new).and_return(trans)
data/spec/server_spec.rb CHANGED
@@ -19,7 +19,6 @@
19
19
  require 'spec_helper'
20
20
 
21
21
  describe 'Server' do
22
-
23
22
  describe Thrift::BaseServer do
24
23
  before(:each) do
25
24
  @processor = double("Processor")
@@ -36,9 +35,9 @@ describe 'Server' do
36
35
  end
37
36
 
38
37
  it "should not serve" do
39
- expect { @server.serve()}.to raise_error(NotImplementedError)
38
+ expect { @server.serve() }.to raise_error(NotImplementedError)
40
39
  end
41
-
40
+
42
41
  it "should provide a reasonable to_s" do
43
42
  expect(@serverTrans).to receive(:to_s).once.and_return("serverTrans")
44
43
  expect(@trans).to receive(:to_s).once.and_return("trans")
@@ -56,14 +55,14 @@ describe 'Server' do
56
55
  @client = double("Client")
57
56
  @server = described_class.new(@processor, @serverTrans, @trans, @prot)
58
57
  end
59
-
58
+
60
59
  it "should provide a reasonable to_s" do
61
60
  expect(@serverTrans).to receive(:to_s).once.and_return("serverTrans")
62
61
  expect(@trans).to receive(:to_s).once.and_return("trans")
63
62
  expect(@prot).to receive(:to_s).once.and_return("prot")
64
63
  expect(@server.to_s).to eq("simple(server(prot(trans(serverTrans))))")
65
64
  end
66
-
65
+
67
66
  it "should serve in the main thread" do
68
67
  expect(@serverTrans).to receive(:listen).ordered
69
68
  expect(@serverTrans).to receive(:accept).exactly(3).times.and_return(@client)
@@ -99,7 +98,7 @@ describe 'Server' do
99
98
  expect(@prot).to receive(:to_s).once.and_return("prot")
100
99
  expect(@server.to_s).to eq("threaded(server(prot(trans(serverTrans))))")
101
100
  end
102
-
101
+
103
102
  it "should serve using threads" do
104
103
  expect(@serverTrans).to receive(:listen).ordered
105
104
  expect(@serverTrans).to receive(:accept).exactly(3).times.and_return(@client)
@@ -137,10 +136,10 @@ describe 'Server' do
137
136
  expect(@prot).to receive(:to_s).once.and_return("prot")
138
137
  expect(@server.to_s).to eq("threadpool(server(prot(trans(server_trans))))")
139
138
  end
140
-
139
+
141
140
  it "should serve inside a thread" do
142
141
  exception_q = @server.instance_variable_get(:@exception_q)
143
- expect_any_instance_of(described_class).to receive(:serve) do
142
+ expect_any_instance_of(described_class).to receive(:serve) do
144
143
  exception_q.push(StandardError.new('ERROR'))
145
144
  end
146
145
  expect { @server.rescuable_serve }.to(raise_error('ERROR'))
@@ -149,7 +148,7 @@ describe 'Server' do
149
148
 
150
149
  it "should avoid running the server twice when retrying rescuable_serve" do
151
150
  exception_q = @server.instance_variable_get(:@exception_q)
152
- expect_any_instance_of(described_class).to receive(:serve) do
151
+ expect_any_instance_of(described_class).to receive(:serve) do
153
152
  exception_q.push(StandardError.new('ERROR1'))
154
153
  exception_q.push(StandardError.new('ERROR2'))
155
154
  end
data/spec/socket_spec.rb CHANGED
@@ -21,7 +21,6 @@ require 'spec_helper'
21
21
  require File.expand_path("#{File.dirname(__FILE__)}/socket_spec_shared")
22
22
 
23
23
  describe 'Socket' do
24
-
25
24
  describe Thrift::Socket do
26
25
  before(:each) do
27
26
  @socket = Thrift::Socket.new
@@ -67,38 +67,101 @@ shared_examples_for "a socket" do
67
67
  @socket.open
68
68
  allow(@handle).to receive(:closed?).and_return(true)
69
69
  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")
70
+ expect { @socket.write("fail") }.to raise_error(Thrift::TransportException, "closed stream") { |e| expect(e.type).to eq(Thrift::TransportException::NOT_OPEN) }
71
+ expect { @socket.read(10) }.to raise_error(Thrift::TransportException, "closed stream") { |e| expect(e.type).to eq(Thrift::TransportException::NOT_OPEN) }
72
72
  end
73
73
 
74
74
  it "should support the timeout accessor for read" do
75
75
  @socket.timeout = 3
76
76
  @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")
77
+ expect(@handle).to receive(:read_nonblock).with(17).and_raise(IO::EAGAINWaitReadable)
78
+ expect(IO).to receive(:select) do |rd, wr, err, timeout|
79
+ expect(rd).to eq([@handle])
80
+ expect(wr).to be_nil
81
+ expect(err).to be_nil
82
+ expect(timeout).to be > 0
83
+ expect(timeout).to be <= 3
84
+ [[@handle], [], []]
85
+ end
86
+ expect(@handle).to receive(:read_nonblock).with(17).and_return("test data")
79
87
  expect(@socket.read(17)).to eq("test data")
80
88
  end
81
89
 
82
90
  it "should support the timeout accessor for write" do
83
91
  @socket.timeout = 3
84
92
  @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)
93
+ write_calls = 0
94
+ expect(@handle).to receive(:write_nonblock).exactly(3).times do |chunk|
95
+ write_calls += 1
96
+ case write_calls
97
+ when 1
98
+ expect(chunk).to eq("test data")
99
+ raise IO::EAGAINWaitWritable
100
+ when 2
101
+ expect(chunk).to eq("test data")
102
+ 4
103
+ when 3
104
+ expect(chunk).to eq(" data")
105
+ 5
106
+ end
107
+ end
108
+ expect(IO).to receive(:select) do |rd, wr, err, timeout|
109
+ expect(rd).to be_nil
110
+ expect(wr).to eq([@handle])
111
+ expect(err).to be_nil
112
+ expect(timeout).to be > 0
113
+ expect(timeout).to be <= 3
114
+ [[], [@handle], []]
115
+ end
88
116
  expect(@socket.write("test data")).to eq(9)
89
117
  end
90
118
 
91
119
  it "should raise an error when read times out" do
92
120
  @socket.timeout = 0.5
93
121
  @socket.open
94
- expect(IO).to receive(:select).once {sleep(0.5); nil}
122
+ expect(@handle).to receive(:read_nonblock).with(17).and_raise(IO::EAGAINWaitReadable)
123
+ expect(IO).to receive(:select).once { sleep(0.6); nil }
95
124
  expect { @socket.read(17) }.to raise_error(Thrift::TransportException) { |e| expect(e.type).to eq(Thrift::TransportException::TIMED_OUT) }
96
125
  end
97
126
 
98
127
  it "should raise an error when write times out" do
99
128
  @socket.timeout = 0.5
100
129
  @socket.open
101
- allow(IO).to receive(:select).with(nil, [@handle], nil, 0.5).and_return(nil)
130
+ expect(@handle).to receive(:write_nonblock).with("test data").and_raise(IO::EAGAINWaitWritable)
131
+ expect(IO).to receive(:select).once { sleep(0.6); nil }
102
132
  expect { @socket.write("test data") }.to raise_error(Thrift::TransportException) { |e| expect(e.type).to eq(Thrift::TransportException::TIMED_OUT) }
103
133
  end
134
+
135
+ it "should read buffered SSL data without waiting on the raw socket again" do
136
+ @socket.timeout = 1
137
+ @socket.open
138
+
139
+ expect(@handle).to receive(:read_nonblock).with(4).ordered.and_raise(IO::EAGAINWaitReadable)
140
+ expect(IO).to receive(:select).once.ordered do |rd, wr, err, timeout|
141
+ expect(rd).to eq([@handle])
142
+ expect(wr).to be_nil
143
+ expect(err).to be_nil
144
+ expect(timeout).to be > 0
145
+ expect(timeout).to be <= 1
146
+ [[@handle], [], []]
147
+ end
148
+ expect(@handle).to receive(:read_nonblock).with(4).ordered.and_return("ABCD")
149
+ expect(@handle).to receive(:read_nonblock).with(5).ordered.and_return("12345")
150
+
151
+ expect(@socket.read(4)).to eq("ABCD")
152
+ expect(@socket.read(5)).to eq("12345")
153
+ end
154
+
155
+ it "should read without timeout using the blocking path" do
156
+ @socket.timeout = nil
157
+ @socket.open
158
+
159
+ expect(IO).not_to receive(:select)
160
+ expect(@handle).not_to receive(:read_nonblock)
161
+ expect(@handle).to receive(:readpartial).with(4).ordered.and_return("ABCD")
162
+ expect(@handle).to receive(:readpartial).with(5).ordered.and_return("12345")
163
+
164
+ expect(@socket.read(4)).to eq("ABCD")
165
+ expect(@socket.read(5)).to eq("12345")
166
+ end
104
167
  end
data/spec/spec_helper.rb CHANGED
@@ -55,7 +55,7 @@ require 'nonblocking_service'
55
55
 
56
56
  module Fixtures
57
57
  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*')
58
+ COMPACT_PROTOCOL_TEST_STRUCT.a_binary = [0, 1, 2, 3, 4, 5, 6, 7, 8].pack('c*')
59
59
  COMPACT_PROTOCOL_TEST_STRUCT.set_byte_map = nil
60
60
  COMPACT_PROTOCOL_TEST_STRUCT.map_byte_map = nil
61
61
  end
@@ -21,12 +21,23 @@ require 'spec_helper'
21
21
  require File.expand_path("#{File.dirname(__FILE__)}/socket_spec_shared")
22
22
 
23
23
  describe 'SSLServerSocket' do
24
-
25
24
  describe Thrift::SSLServerSocket do
26
25
  before(:each) do
27
26
  @socket = Thrift::SSLServerSocket.new(1234)
28
27
  end
29
28
 
29
+ it "should delegate to_io to the underlying SSL server handle" do
30
+ tcp_server = double("TCPServer")
31
+ ssl_server = double("SSLServer")
32
+
33
+ allow(TCPServer).to receive(:new).with(nil, 1234).and_return(tcp_server)
34
+ allow(OpenSSL::SSL::SSLServer).to receive(:new).with(tcp_server, nil).and_return(ssl_server)
35
+ allow(ssl_server).to receive(:to_io).and_return(tcp_server)
36
+
37
+ @socket.listen
38
+ expect(@socket.to_io).to eq(tcp_server)
39
+ end
40
+
30
41
  it "should provide a reasonable to_s" do
31
42
  expect(@socket.to_s).to eq("ssl(socket(:1234))")
32
43
  end
@@ -21,7 +21,6 @@ require 'spec_helper'
21
21
  require File.expand_path("#{File.dirname(__FILE__)}/socket_spec_shared")
22
22
 
23
23
  describe 'SSLSocket' do
24
-
25
24
  describe Thrift::SSLSocket do
26
25
  before(:each) do
27
26
  @context = OpenSSL::SSL::SSLContext.new
@@ -35,6 +34,7 @@ describe 'SSLSocket' do
35
34
  allow(@handle).to receive(:connect_nonblock)
36
35
  allow(@handle).to receive(:close)
37
36
  allow(@handle).to receive(:post_connection_check)
37
+ allow(@handle).to receive(:to_io).and_return(@simple_socket_handle)
38
38
 
39
39
  allow(::Socket).to receive(:new).and_return(@simple_socket_handle)
40
40
  allow(OpenSSL::SSL::SSLSocket).to receive(:new).and_return(@handle)
@@ -71,6 +71,15 @@ describe 'SSLSocket' do
71
71
  expect(Thrift::SSLSocket.new('localhost', 8080, 5, @context).ssl_context).to eq(@context)
72
72
  end
73
73
 
74
+ it "should delegate to_io to the underlying SSL socket handle" do
75
+ @socket.open
76
+ expect(@socket.to_io).to eq(@simple_socket_handle)
77
+ end
78
+
79
+ it "should raise IOError when to_io is called on a closed stream" do
80
+ expect { @socket.to_io }.to raise_error(IOError, 'closed stream')
81
+ end
82
+
74
83
  it "should provide a reasonable to_s" do
75
84
  expect(Thrift::SSLSocket.new('myhost', 8090).to_s).to eq("ssl(socket(myhost:8090))")
76
85
  end
@@ -20,7 +20,6 @@
20
20
  require 'spec_helper'
21
21
 
22
22
  describe 'StructNestedContainers' do
23
-
24
23
  def with_type_checking
25
24
  saved_type_checking, Thrift.type_checking = Thrift.type_checking, true
26
25
  begin
@@ -166,7 +165,7 @@ describe 'StructNestedContainers' do
166
165
  with_type_checking do
167
166
  a, b = SpecNamespace::NestedMapInMapKey.new, SpecNamespace::NestedMapInMapKey.new
168
167
  [a, b].each do |thrift_struct|
169
- thrift_struct.value = { { 1 => 2, 3 => 4} => 1, {2 => 3, 4 => 5} => 2 }
168
+ thrift_struct.value = { { 1 => 2, 3 => 4} => 1, {2 => 3, 4 => 5} => 2 }
170
169
  thrift_struct.validate
171
170
  end
172
171
  expect(a).to eq(b)
data/spec/struct_spec.rb CHANGED
@@ -20,11 +20,10 @@
20
20
  require 'spec_helper'
21
21
 
22
22
  describe 'Struct' do
23
-
24
23
  describe Thrift::Struct do
25
24
  it "should iterate over all fields properly" do
26
25
  fields = {}
27
- SpecNamespace::Foo.new.each_field { |fid,field_info| fields[fid] = field_info }
26
+ SpecNamespace::Foo.new.each_field { |fid, field_info| fields[fid] = field_info }
28
27
  expect(fields).to eq(SpecNamespace::Foo::FIELDS)
29
28
  end
30
29
 
@@ -51,10 +50,10 @@ describe 'Struct' do
51
50
  begin
52
51
  struct = SpecNamespace::Foo.new
53
52
  struct.ints << 17
54
- expect(SpecNamespace::Foo.new.ints).to eq([1,2,2,3])
53
+ expect(SpecNamespace::Foo.new.ints).to eq([1, 2, 2, 3])
55
54
  ensure
56
55
  # ensure no leakage to other tests
57
- SpecNamespace::Foo::FIELDS[4][:default] = [1,2,2,3]
56
+ SpecNamespace::Foo::FIELDS[4][:default] = [1, 2, 2, 3]
58
57
  end
59
58
  end
60
59
 
@@ -142,6 +141,34 @@ describe 'Struct' do
142
141
  expect(struct.shorts).to eq(Set.new([3, 2]))
143
142
  end
144
143
 
144
+ it "rejects negative container sizes while reading" do
145
+ struct = SpecNamespace::Foo.new
146
+ prot = Thrift::BaseProtocol.new(double("transport"))
147
+
148
+ expect(prot).to receive(:read_list_begin).and_return([Thrift::Types::I32, -1])
149
+
150
+ expect {
151
+ struct.send(:read_field, prot, SpecNamespace::Foo::FIELDS[4])
152
+ }.to raise_error(Thrift::ProtocolException, "Negative size") { |error|
153
+ expect(error.type).to eq(Thrift::ProtocolException::NEGATIVE_SIZE)
154
+ }
155
+ end
156
+
157
+ it "does not preallocate arrays from declared list sizes" do
158
+ struct = SpecNamespace::Foo.new
159
+ prot = Thrift::BaseProtocol.new(double("transport"))
160
+ declared_size = 1 << 30
161
+ sentinel = RuntimeError.new("stop after first element")
162
+
163
+ expect(prot).to receive(:read_list_begin).and_return([Thrift::Types::I32, declared_size])
164
+ expect(prot).to receive(:read_i32).and_raise(sentinel)
165
+ expect(Array).not_to receive(:new).with(declared_size)
166
+
167
+ expect {
168
+ struct.send(:read_field, prot, SpecNamespace::Foo::FIELDS[4])
169
+ }.to raise_error(sentinel)
170
+ end
171
+
145
172
  it "should serialize false boolean fields correctly" do
146
173
  b = SpecNamespace::BoolStruct.new(:yesno => false)
147
174
  prot = Thrift::BinaryProtocol.new(Thrift::MemoryBufferTransport.new)
@@ -181,7 +208,7 @@ describe 'Struct' do
181
208
  end
182
209
 
183
210
  it "should write itself to the wire" do
184
- prot = Thrift::BaseProtocol.new(double("transport")) #mock("Protocol")
211
+ prot = Thrift::BaseProtocol.new(double("transport")) # mock("Protocol")
185
212
  expect(prot).to receive(:write_struct_begin).with("SpecNamespace::Foo")
186
213
  expect(prot).to receive(:write_struct_begin).with("SpecNamespace::Hello")
187
214
  expect(prot).to receive(:write_struct_end).twice
@@ -227,7 +254,7 @@ describe 'Struct' do
227
254
  it "should support optional type-checking in Thrift::Struct.new" do
228
255
  Thrift.type_checking = true
229
256
  begin
230
- expect { SpecNamespace::Hello.new(:greeting => 3) }.to raise_error(Thrift::TypeError, /Expected Types::STRING, received (Integer|Fixnum) for field greeting/)
257
+ expect { SpecNamespace::Hello.new(:greeting => 3) }.to raise_error(Thrift::TypeError, "Expected Types::STRING, received Integer for field greeting")
231
258
  ensure
232
259
  Thrift.type_checking = false
233
260
  end
@@ -238,7 +265,7 @@ describe 'Struct' do
238
265
  Thrift.type_checking = true
239
266
  begin
240
267
  hello = SpecNamespace::Hello.new
241
- expect { hello.greeting = 3 }.to raise_error(Thrift::TypeError, /Expected Types::STRING, received (Integer|Fixnum) for field greeting/)
268
+ expect { hello.greeting = 3 }.to raise_error(Thrift::TypeError, "Expected Types::STRING, received Integer for field greeting")
242
269
  ensure
243
270
  Thrift.type_checking = false
244
271
  end
@@ -259,9 +286,9 @@ describe 'Struct' do
259
286
  prot = Thrift::BaseProtocol.new(double("trans"))
260
287
  expect(prot).to receive(:write_struct_begin).with("SpecNamespace::Xception")
261
288
  expect(prot).to receive(:write_struct_end)
262
- expect(prot).to receive(:write_field_begin).with('message', Thrift::Types::STRING, 1)#, "something happened")
289
+ expect(prot).to receive(:write_field_begin).with('message', Thrift::Types::STRING, 1)
263
290
  expect(prot).to receive(:write_string).with("something happened")
264
- expect(prot).to receive(:write_field_begin).with('code', Thrift::Types::I32, 2)#, 1)
291
+ expect(prot).to receive(:write_field_begin).with('code', Thrift::Types::I32, 2)
265
292
  expect(prot).to receive(:write_i32).with(1)
266
293
  expect(prot).to receive(:write_field_stop)
267
294
  expect(prot).to receive(:write_field_end).twice
@@ -289,5 +316,82 @@ describe 'Struct' do
289
316
  e.write(prot)
290
317
  end
291
318
  end
319
+
320
+ it "should handle UUID fields in structs" do
321
+ struct = SpecNamespace::Foo.new(
322
+ simple: 42,
323
+ words: 'test',
324
+ opt_uuid: '550e8400-e29b-41d4-a716-446655440000'
325
+ )
326
+
327
+ trans = Thrift::MemoryBufferTransport.new
328
+ prot = Thrift::BinaryProtocol.new(trans)
329
+
330
+ struct.write(prot)
331
+
332
+ result = SpecNamespace::Foo.new
333
+ result.read(prot)
334
+
335
+ expect(result.simple).to eq(42)
336
+ expect(result.words).to eq('test')
337
+ expect(result.opt_uuid).to eq('550e8400-e29b-41d4-a716-446655440000')
338
+ end
339
+
340
+ it "should handle optional UUID fields when unset" do
341
+ struct = SpecNamespace::Foo.new(simple: 42, words: 'test')
342
+ expect(struct.opt_uuid).to be_nil
343
+ expect(struct.opt_uuid?).to be_falsey
344
+ end
345
+
346
+ it "should handle list of UUIDs in SimpleList" do
347
+ uuids = ['550e8400-e29b-41d4-a716-446655440000', '6ba7b810-9dad-11d1-80b4-00c04fd430c8']
348
+ struct = SpecNamespace::SimpleList.new(uuids: uuids)
349
+
350
+ trans = Thrift::MemoryBufferTransport.new
351
+ prot = Thrift::CompactProtocol.new(trans)
352
+
353
+ struct.write(prot)
354
+
355
+ result = SpecNamespace::SimpleList.new
356
+ result.read(prot)
357
+
358
+ expect(result.uuids).to eq(uuids)
359
+ end
360
+
361
+ it "should normalize UUID case to lowercase" do
362
+ struct = SpecNamespace::Foo.new(opt_uuid: '550E8400-E29B-41D4-A716-446655440000')
363
+
364
+ trans = Thrift::MemoryBufferTransport.new
365
+ prot = Thrift::BinaryProtocol.new(trans)
366
+
367
+ struct.write(prot)
368
+
369
+ result = SpecNamespace::Foo.new
370
+ result.read(prot)
371
+
372
+ expect(result.opt_uuid).to eq('550e8400-e29b-41d4-a716-446655440000')
373
+ end
374
+
375
+ it "should handle UUID alongside other types in SimpleList" do
376
+ struct = SpecNamespace::SimpleList.new(
377
+ bools: [true, false],
378
+ i32s: [1, 2, 3],
379
+ strings: ['hello', 'world'],
380
+ uuids: ['550e8400-e29b-41d4-a716-446655440000', '00000000-0000-0000-0000-000000000000']
381
+ )
382
+
383
+ trans = Thrift::MemoryBufferTransport.new
384
+ prot = Thrift::BinaryProtocol.new(trans)
385
+
386
+ struct.write(prot)
387
+
388
+ result = SpecNamespace::SimpleList.new
389
+ result.read(prot)
390
+
391
+ expect(result.bools).to eq([true, false])
392
+ expect(result.i32s).to eq([1, 2, 3])
393
+ expect(result.strings).to eq(['hello', 'world'])
394
+ expect(result.uuids).to eq(['550e8400-e29b-41d4-a716-446655440000', '00000000-0000-0000-0000-000000000000'])
395
+ end
292
396
  end
293
397
  end
@@ -0,0 +1,54 @@
1
+ #
2
+ # Licensed to the Apache Software Foundation (ASF) under one
3
+ # or more contributor license agreements. See the NOTICE file
4
+ # distributed with this work for additional information
5
+ # regarding copyright ownership. The ASF licenses this file
6
+ # to you under the Apache License, Version 2.0 (the
7
+ # "License"); you may not use this file except in compliance
8
+ # with the License. You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing,
13
+ # software distributed under the License is distributed on an
14
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
+ # KIND, either express or implied. See the License for the
16
+ # specific language governing permissions and limitations
17
+ # under the License.
18
+ #
19
+
20
+ module HeaderProtocolHelper
21
+ def varint32(n)
22
+ bytes = []
23
+ loop do
24
+ if (n & ~0x7f) == 0
25
+ bytes << n
26
+ break
27
+ else
28
+ bytes << ((n & 0x7f) | 0x80)
29
+ n >>= 7
30
+ end
31
+ end
32
+ bytes.pack('C*')
33
+ end
34
+
35
+ def build_header_frame(header_data, payload = Thrift::Bytes.empty_byte_buffer, header_words: nil)
36
+ header_data = Thrift::Bytes.force_binary_encoding(header_data)
37
+ if header_words.nil?
38
+ padding = (4 - (header_data.bytesize % 4)) % 4
39
+ header_data += "\x00" * padding
40
+ header_words = header_data.bytesize / 4
41
+ end
42
+
43
+ frame_size = 2 + 2 + 4 + 2 + header_data.bytesize + payload.bytesize
44
+ frame = Thrift::Bytes.empty_byte_buffer
45
+ frame << [frame_size].pack('N')
46
+ frame << [Thrift::HeaderTransport::HEADER_MAGIC].pack('n')
47
+ frame << [0].pack('n')
48
+ frame << [0].pack('N')
49
+ frame << [header_words].pack('n')
50
+ frame << header_data
51
+ frame << payload
52
+ frame
53
+ end
54
+ end