thrift 0.23.0 → 0.25.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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +203 -40
  3. data/ext/binary_protocol_accelerated.c +130 -40
  4. data/ext/bytes.c +14 -0
  5. data/ext/compact_protocol.c +184 -91
  6. data/ext/constants.h +3 -0
  7. data/ext/extconf.rb +12 -4
  8. data/ext/macros.h +11 -0
  9. data/ext/memory_buffer.c +67 -12
  10. data/ext/struct.c +192 -71
  11. data/ext/thrift_native.c +7 -0
  12. data/lib/thrift/bytes.rb +6 -3
  13. data/lib/thrift/client.rb +31 -33
  14. data/lib/thrift/exceptions.rb +11 -8
  15. data/lib/thrift/multiplexed_processor.rb +17 -8
  16. data/lib/thrift/processor.rb +25 -3
  17. data/lib/thrift/protocol/base_protocol.rb +66 -31
  18. data/lib/thrift/protocol/binary_protocol.rb +80 -37
  19. data/lib/thrift/protocol/binary_protocol_accelerated.rb +7 -2
  20. data/lib/thrift/protocol/compact_protocol.rb +204 -88
  21. data/lib/thrift/protocol/header_protocol.rb +15 -3
  22. data/lib/thrift/protocol/json_protocol.rb +158 -109
  23. data/lib/thrift/protocol/multiplexed_protocol.rb +3 -3
  24. data/lib/thrift/protocol/protocol_decorator.rb +6 -2
  25. data/lib/thrift/serializer/deserializer.rb +1 -0
  26. data/lib/thrift/serializer/serializer.rb +7 -0
  27. data/lib/thrift/server/base_server.rb +11 -2
  28. data/lib/thrift/server/nonblocking_server.rb +69 -20
  29. data/lib/thrift/server/rack_application.rb +69 -0
  30. data/lib/thrift/server/simple_server.rb +27 -15
  31. data/lib/thrift/server/thin_http_server.rb +21 -44
  32. data/lib/thrift/server/thread_pool_server.rb +21 -17
  33. data/lib/thrift/server/threaded_server.rb +22 -18
  34. data/lib/thrift/struct.rb +34 -18
  35. data/lib/thrift/struct_union.rb +37 -36
  36. data/lib/thrift/thrift_native.rb +2 -1
  37. data/lib/thrift/transport/base_server_transport.rb +3 -0
  38. data/lib/thrift/transport/base_transport.rb +26 -4
  39. data/lib/thrift/transport/buffered_transport.rb +5 -4
  40. data/lib/thrift/transport/framed_transport.rb +33 -7
  41. data/lib/thrift/transport/header_transport.rb +159 -33
  42. data/lib/thrift/transport/http_client_transport.rb +28 -14
  43. data/lib/thrift/transport/io_stream_transport.rb +3 -1
  44. data/lib/thrift/transport/memory_buffer_transport.rb +14 -9
  45. data/lib/thrift/transport/server_socket.rb +10 -8
  46. data/lib/thrift/transport/socket.rb +85 -48
  47. data/lib/thrift/transport/ssl_server_socket.rb +67 -6
  48. data/lib/thrift/transport/ssl_socket.rb +73 -14
  49. data/lib/thrift/transport/unix_server_socket.rb +6 -2
  50. data/lib/thrift/transport/unix_socket.rb +5 -6
  51. data/lib/thrift/types.rb +23 -22
  52. data/lib/thrift/union.rb +21 -11
  53. data/lib/thrift/uuid.rb +9 -8
  54. data/lib/thrift.rb +40 -39
  55. metadata +7 -266
  56. data/benchmark/Benchmark.thrift +0 -24
  57. data/benchmark/benchmark.rb +0 -285
  58. data/benchmark/client.rb +0 -117
  59. data/benchmark/server.rb +0 -120
  60. data/benchmark/thin_server.rb +0 -45
  61. data/lib/thrift/server/mongrel_http_server.rb +0 -60
  62. data/spec/BaseService.thrift +0 -27
  63. data/spec/ExtendedService.thrift +0 -25
  64. data/spec/Referenced.thrift +0 -44
  65. data/spec/ThriftNamespacedSpec.thrift +0 -53
  66. data/spec/ThriftSpec.thrift +0 -187
  67. data/spec/base_protocol_spec.rb +0 -224
  68. data/spec/base_transport_spec.rb +0 -387
  69. data/spec/binary_protocol_accelerated_spec.rb +0 -46
  70. data/spec/binary_protocol_spec.rb +0 -72
  71. data/spec/binary_protocol_spec_shared.rb +0 -445
  72. data/spec/bytes_spec.rb +0 -99
  73. data/spec/client_spec.rb +0 -166
  74. data/spec/compact_protocol_spec.rb +0 -196
  75. data/spec/constants_demo_spec.rb +0 -101
  76. data/spec/exception_spec.rb +0 -140
  77. data/spec/flat_spec.rb +0 -62
  78. data/spec/header_protocol_spec.rb +0 -475
  79. data/spec/header_transport_spec.rb +0 -386
  80. data/spec/http_client_spec.rb +0 -159
  81. data/spec/json_protocol_spec.rb +0 -552
  82. data/spec/namespaced_spec.rb +0 -66
  83. data/spec/nonblocking_server_spec.rb +0 -361
  84. data/spec/processor_spec.rb +0 -79
  85. data/spec/serializer_spec.rb +0 -66
  86. data/spec/server_socket_spec.rb +0 -84
  87. data/spec/server_spec.rb +0 -186
  88. data/spec/socket_spec.rb +0 -67
  89. data/spec/socket_spec_shared.rb +0 -167
  90. data/spec/spec_helper.rb +0 -69
  91. data/spec/ssl_server_socket_spec.rb +0 -45
  92. data/spec/ssl_socket_spec.rb +0 -87
  93. data/spec/struct_nested_containers_spec.rb +0 -190
  94. data/spec/struct_spec.rb +0 -397
  95. data/spec/support/header_protocol_helper.rb +0 -54
  96. data/spec/thin_http_server_spec.rb +0 -126
  97. data/spec/types_spec.rb +0 -117
  98. data/spec/union_spec.rb +0 -272
  99. data/spec/unix_socket_spec.rb +0 -115
  100. data/spec/uuid_validation_spec.rb +0 -238
  101. data/test/fuzz/Makefile.am +0 -173
  102. data/test/fuzz/README.md +0 -149
  103. data/test/fuzz/fuzz_common.rb +0 -95
  104. data/test/fuzz/fuzz_parse_binary_protocol.rb +0 -22
  105. data/test/fuzz/fuzz_parse_binary_protocol_accelerated.rb +0 -22
  106. data/test/fuzz/fuzz_parse_binary_protocol_accelerated_harness.rb +0 -22
  107. data/test/fuzz/fuzz_parse_binary_protocol_harness.rb +0 -22
  108. data/test/fuzz/fuzz_parse_compact_protocol.rb +0 -22
  109. data/test/fuzz/fuzz_parse_compact_protocol_harness.rb +0 -22
  110. data/test/fuzz/fuzz_parse_json_protocol.rb +0 -22
  111. data/test/fuzz/fuzz_parse_json_protocol_harness.rb +0 -22
  112. data/test/fuzz/fuzz_roundtrip_binary_protocol.rb +0 -22
  113. data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated.rb +0 -22
  114. data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated_harness.rb +0 -22
  115. data/test/fuzz/fuzz_roundtrip_binary_protocol_harness.rb +0 -22
  116. data/test/fuzz/fuzz_roundtrip_compact_protocol.rb +0 -22
  117. data/test/fuzz/fuzz_roundtrip_compact_protocol_harness.rb +0 -22
  118. data/test/fuzz/fuzz_roundtrip_json_protocol.rb +0 -22
  119. data/test/fuzz/fuzz_roundtrip_json_protocol_harness.rb +0 -22
  120. data/test/fuzz/fuzz_tracer.rb +0 -28
data/lib/thrift/struct.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
@@ -17,7 +18,8 @@
17
18
  # under the License.
18
19
  #
19
20
 
20
- require 'set'
21
+ require "set"
22
+ require "thrift/struct_union"
21
23
 
22
24
  module Thrift
23
25
  module Struct
@@ -51,7 +53,7 @@ module Thrift
51
53
  end
52
54
  end
53
55
 
54
- yield self if block_given?
56
+ yield self if block
55
57
  end
56
58
 
57
59
  def fields_with_default_values
@@ -80,19 +82,31 @@ module Thrift
80
82
  "<#{self.class} #{fields.join(", ")}>"
81
83
  end
82
84
 
83
- def read(iprot)
85
+ def read(iprot, remaining_depth = DEFAULT_RECURSION_DEPTH)
86
+ raise ProtocolException.new(ProtocolException::DEPTH_LIMIT, "Maximum recursion depth exceeded") if remaining_depth <= 0
87
+ unless instance_variables.empty?
88
+ defaults = fields_with_default_values
89
+ struct_fields.each_value do |field_info|
90
+ instance_variable_set("@#{field_info[:name]}", nil)
91
+ end
92
+ defaults.each do |name, default_value|
93
+ instance_variable_set("@#{name}", (default_value.dup rescue default_value))
94
+ end
95
+ end
96
+
84
97
  iprot.read_struct_begin
85
98
  loop do
86
- fname, ftype, fid = iprot.read_field_begin
99
+ _, ftype, fid = iprot.read_field_begin
87
100
  break if (ftype == Types::STOP)
88
- handle_message(iprot, fid, ftype)
101
+ handle_message(iprot, fid, ftype, remaining_depth)
89
102
  iprot.read_field_end
90
103
  end
91
104
  iprot.read_struct_end
92
105
  validate
93
106
  end
94
107
 
95
- def write(oprot)
108
+ def write(oprot, remaining_depth = DEFAULT_RECURSION_DEPTH)
109
+ raise ProtocolException.new(ProtocolException::DEPTH_LIMIT, "Maximum recursion depth exceeded") if remaining_depth <= 0
96
110
  validate
97
111
  oprot.write_struct_begin(self.class.name)
98
112
  each_field do |fid, field_info|
@@ -102,8 +116,10 @@ module Thrift
102
116
  unless value.nil?
103
117
  if is_container? type
104
118
  oprot.write_field_begin(name, type, fid)
105
- write_container(oprot, value, field_info)
119
+ write_container(oprot, value, field_info, remaining_depth)
106
120
  oprot.write_field_end
121
+ elsif type == Types::STRUCT
122
+ oprot.write_field(field_info, fid, value, remaining_depth)
107
123
  else
108
124
  oprot.write_field(field_info, fid, value)
109
125
  end
@@ -114,10 +130,10 @@ module Thrift
114
130
  end
115
131
 
116
132
  def ==(other)
117
- return false if other.nil?
133
+ return false unless other.instance_of?(self.class)
118
134
  each_field do |fid, field_info|
119
135
  name = field_info[:name]
120
- return false unless other.respond_to?(name) && self.send(name) == other.send(name)
136
+ return false unless other.respond_to?(name) && send(name) == other.send(name)
121
137
  end
122
138
  true
123
139
  end
@@ -131,8 +147,8 @@ module Thrift
131
147
  total = 17
132
148
  each_field do |fid, field_info|
133
149
  name = field_info[:name]
134
- value = self.send(name)
135
- total = (total * 37 + value.hash) & 0xffffffff
150
+ value = send(name)
151
+ total = ((total * 37) + value.hash) & 0xffffffff
136
152
  end
137
153
  total
138
154
  end
@@ -144,7 +160,7 @@ module Thrift
144
160
  else
145
161
  each_field do |fid, field_info|
146
162
  name = field_info[:name]
147
- diffs << "#{name} differs!" unless self.instance_variable_get("@#{name}") == other.instance_variable_get("@#{name}")
163
+ diffs << "#{name} differs!" unless instance_variable_get("@#{name}") == other.instance_variable_get("@#{name}")
148
164
  end
149
165
  end
150
166
  diffs
@@ -168,14 +184,14 @@ module Thrift
168
184
 
169
185
  def self.qmark_isset_method(klass, field_info)
170
186
  klass.send :define_method, "#{field_info[:name]}?" do
171
- !self.send(field_info[:name].to_sym).nil?
187
+ !send(field_info[:name].to_sym).nil?
172
188
  end
173
189
  end
174
190
 
175
191
  def <=>(other)
176
192
  if self.class == other.class
177
193
  each_field do |fid, field_info|
178
- v1 = self.send(field_info[:name])
194
+ v1 = send(field_info[:name])
179
195
  v1_set = !v1.nil?
180
196
  v2 = other.send(field_info[:name])
181
197
  v2_set = !v2.nil?
@@ -217,17 +233,17 @@ module Thrift
217
233
  else
218
234
  # call the Struct initializer first with no args
219
235
  # this will set our field default values
220
- method(:struct_initialize).call()
236
+ method(:struct_initialize).call
221
237
  # now give it to the exception
222
- self.class.send(:class_variable_get, :'@@__thrift_struct_real_initialize').bind(self).call(*args, &block) if args.size > 0
238
+ self.class.send(:class_variable_get, :'@@__thrift_struct_real_initialize').bind_call(self, *args, &block) if args.size > 0
223
239
  # self.class.instance_method(:initialize).bind(self).call(*args, &block)
224
240
  end
225
241
  end
226
242
 
227
- def handle_message(iprot, fid, ftype)
243
+ def handle_message(iprot, fid, ftype, remaining_depth)
228
244
  field = struct_fields[fid]
229
245
  if field and field[:type] == ftype
230
- value = read_field(iprot, field)
246
+ value = read_field(iprot, field, remaining_depth)
231
247
  instance_variable_set("@#{field[:name]}", value)
232
248
  else
233
249
  iprot.skip(ftype)
@@ -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
@@ -16,16 +17,12 @@
16
17
  # specific language governing permissions and limitations
17
18
  # under the License.
18
19
  #
19
- require 'set'
20
+ require "set"
21
+ require "thrift/exceptions"
22
+ require "thrift/types"
20
23
 
21
24
  module Thrift
22
25
  module Struct_Union
23
- def validate_container_size(size)
24
- return size unless size < 0
25
-
26
- raise ProtocolException.new(ProtocolException::NEGATIVE_SIZE, 'Negative size')
27
- end
28
-
29
26
  def name_to_id(name)
30
27
  names_to_ids = self.class.instance_variable_get(:@names_to_ids)
31
28
  unless names_to_ids
@@ -54,16 +51,17 @@ module Thrift
54
51
  end
55
52
  end
56
53
 
57
- def read_field(iprot, field = {})
54
+ def read_field(iprot, field = {}, remaining_depth = DEFAULT_RECURSION_DEPTH)
58
55
  case field[:type]
59
56
  when Types::STRUCT
57
+ remaining_depth -= 1
60
58
  value = field[:class].new
61
- value.read(iprot)
59
+ value.read(iprot, remaining_depth)
62
60
  when Types::MAP
63
61
  key_type, val_type, size = iprot.read_map_begin
64
- validate_container_size(size)
62
+ iprot.validate_container_size(size)
65
63
  # Skip the map contents if the declared key or value types don't match the expected ones.
66
- if (size != 0 && (key_type != field[:key][:type] || val_type != field[:value][:type]))
64
+ if size != 0 && (key_type != field[:key][:type] || val_type != field[:value][:type])
67
65
  size.times do
68
66
  iprot.skip(key_type)
69
67
  iprot.skip(val_type)
@@ -72,15 +70,15 @@ module Thrift
72
70
  else
73
71
  value = {}
74
72
  size.times do
75
- k = read_field(iprot, field_info(field[:key]))
76
- v = read_field(iprot, field_info(field[:value]))
73
+ k = read_field(iprot, field_info(field[:key]), remaining_depth)
74
+ v = read_field(iprot, field_info(field[:value]), remaining_depth)
77
75
  value[k] = v
78
76
  end
79
77
  end
80
78
  iprot.read_map_end
81
79
  when Types::LIST
82
80
  e_type, size = iprot.read_list_begin
83
- validate_container_size(size)
81
+ iprot.validate_container_size(size)
84
82
  # Skip the list contents if the declared element type doesn't match the expected one.
85
83
  if (e_type != field[:element][:type])
86
84
  size.times do
@@ -90,13 +88,13 @@ module Thrift
90
88
  else
91
89
  value = []
92
90
  size.times do
93
- value << read_field(iprot, field_info(field[:element]))
91
+ value << read_field(iprot, field_info(field[:element]), remaining_depth)
94
92
  end
95
93
  end
96
94
  iprot.read_list_end
97
95
  when Types::SET
98
96
  e_type, size = iprot.read_set_begin
99
- validate_container_size(size)
97
+ iprot.validate_container_size(size)
100
98
  # Skip the set contents if the declared element type doesn't match the expected one.
101
99
  if (e_type != field[:element][:type])
102
100
  size.times do
@@ -105,7 +103,7 @@ module Thrift
105
103
  else
106
104
  value = Set.new
107
105
  size.times do
108
- element = read_field(iprot, field_info(field[:element]))
106
+ element = read_field(iprot, field_info(field[:element]), remaining_depth)
109
107
  value << element
110
108
  end
111
109
  end
@@ -116,33 +114,36 @@ module Thrift
116
114
  value
117
115
  end
118
116
 
119
- def write_data(oprot, value, field)
117
+ def write_data(oprot, value, field, remaining_depth = DEFAULT_RECURSION_DEPTH)
120
118
  if is_container? field[:type]
121
- write_container(oprot, value, field)
119
+ write_container(oprot, value, field, remaining_depth)
120
+ elsif field[:type] == Types::STRUCT
121
+ remaining_depth -= 1
122
+ value.write(oprot, remaining_depth)
122
123
  else
123
124
  oprot.write_type(field, value)
124
125
  end
125
126
  end
126
127
 
127
- def write_container(oprot, value, field = {})
128
+ def write_container(oprot, value, field = {}, remaining_depth = DEFAULT_RECURSION_DEPTH)
128
129
  case field[:type]
129
130
  when Types::MAP
130
131
  oprot.write_map_begin(field[:key][:type], field[:value][:type], value.size)
131
132
  value.each do |k, v|
132
- write_data(oprot, k, field[:key])
133
- write_data(oprot, v, field[:value])
133
+ write_data(oprot, k, field[:key], remaining_depth)
134
+ write_data(oprot, v, field[:value], remaining_depth)
134
135
  end
135
136
  oprot.write_map_end
136
137
  when Types::LIST
137
138
  oprot.write_list_begin(field[:element][:type], value.size)
138
139
  value.each do |elem|
139
- write_data(oprot, elem, field[:element])
140
+ write_data(oprot, elem, field[:element], remaining_depth)
140
141
  end
141
142
  oprot.write_list_end
142
143
  when Types::SET
143
144
  oprot.write_set_begin(field[:element][:type], value.size)
144
145
  value.each do |v,| # the , is to preserve compatibility with the old Hash-style sets
145
- write_data(oprot, v, field[:element])
146
+ write_data(oprot, v, field[:element], remaining_depth)
146
147
  end
147
148
  oprot.write_set_end
148
149
  else
@@ -159,11 +160,13 @@ module Thrift
159
160
  end
160
161
 
161
162
  def field_info(field)
162
- { :type => field[:type],
163
- :class => field[:class],
164
- :key => field[:key],
165
- :value => field[:value],
166
- :element => field[:element] }
163
+ {
164
+ type: field[:type],
165
+ class: field[:class],
166
+ key: field[:key],
167
+ value: field[:value],
168
+ element: field[:element],
169
+ }
167
170
  end
168
171
 
169
172
  def inspect_field(value, field_info)
@@ -171,9 +174,8 @@ module Thrift
171
174
  "#{enum_class.const_get(:VALUE_MAP)[value]} (#{value})"
172
175
  elsif value.is_a? Hash
173
176
  if field_info[:type] == Types::MAP
174
- map_buf = []
175
- value.each do |k, v|
176
- map_buf << inspect_field(k, field_info[:key]) + ": " + inspect_field(v, field_info[:value])
177
+ map_buf = value.map do |k, v|
178
+ inspect_field(k, field_info[:key]) + ": " + inspect_field(v, field_info[:value])
177
179
  end
178
180
  "{" + map_buf.join(", ") + "}"
179
181
  else
@@ -185,16 +187,15 @@ module Thrift
185
187
  elsif value.is_a? Set
186
188
  inspect_collection(value, field_info)
187
189
  elsif value.is_a?(String) && field_info[:binary]
188
- value.unpack("H*").first
190
+ value.unpack1("H*")
189
191
  else
190
192
  value.inspect
191
193
  end
192
194
  end
193
195
 
194
196
  def inspect_collection(collection, field_info)
195
- buf = []
196
- collection.each do |k|
197
- buf << inspect_field(k, field_info[:element])
197
+ buf = collection.map do |element|
198
+ inspect_field(element, field_info[:element])
198
199
  end
199
200
  "[" + buf.join(", ") + "]"
200
201
  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,5 +21,5 @@
20
21
  begin
21
22
  require "thrift_native"
22
23
  rescue LoadError
23
- puts "Unable to load thrift_native extension. Defaulting to pure Ruby libraries."
24
+ warn "Unable to load thrift_native extension. Defaulting to pure Ruby libraries."
24
25
  end
@@ -1,4 +1,5 @@
1
1
  # encoding: ascii-8bit
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
@@ -20,6 +21,8 @@
20
21
 
21
22
  module Thrift
22
23
  class BaseServerTransport
24
+ DEFAULT_CLIENT_TIMEOUT = 5
25
+
23
26
  def listen
24
27
  raise NotImplementedError
25
28
  end
@@ -1,4 +1,5 @@
1
1
  # encoding: ascii-8bit
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
@@ -25,6 +26,8 @@ module Thrift
25
26
  ALREADY_OPEN = 2
26
27
  TIMED_OUT = 3
27
28
  END_OF_FILE = 4
29
+ NEGATIVE_SIZE = 5
30
+ SIZE_LIMIT = 6
28
31
 
29
32
  attr_reader :type
30
33
 
@@ -53,6 +56,11 @@ module Thrift
53
56
 
54
57
  def close; end
55
58
 
59
+ # Returns true when the transport needs protocol message boundary notifications.
60
+ def message_boundaries?
61
+ false
62
+ end
63
+
56
64
  # Reads a number of bytes from the transports. The String returned will have a BINARY (aka ASCII-8BIT) encoding.
57
65
  #
58
66
  # sz - The number of bytes to read from the transport.
@@ -65,7 +73,7 @@ module Thrift
65
73
  # Returns an unsigned byte as a Integer in the range (0..255).
66
74
  def read_byte
67
75
  buf = read_all(1)
68
- return Bytes.get_string_byte(buf, 0)
76
+ Bytes.get_string_byte(buf, 0)
69
77
  end
70
78
 
71
79
  # Reads size bytes and copies them into buffer[0..size].
@@ -80,11 +88,25 @@ module Thrift
80
88
  end
81
89
 
82
90
  def read_all(size)
83
- return Bytes.empty_byte_buffer if size <= 0
91
+ raise TransportException.new(TransportException::NEGATIVE_SIZE, "Negative size") unless size >= 0
92
+ return Bytes.empty_byte_buffer if size == 0
93
+
84
94
  buf = read(size)
95
+ if buf.nil? || buf.empty?
96
+ raise TransportException.new(TransportException::END_OF_FILE, "No more data available")
97
+ end
98
+
99
+ buf = Bytes.force_binary_encoding(buf)
100
+ return buf if buf.length >= size
101
+
102
+ buf = buf.dup if buf.frozen?
85
103
  while (buf.length < size)
86
104
  chunk = read(size - buf.length)
87
- buf << chunk
105
+ if chunk.nil? || chunk.empty?
106
+ raise TransportException.new(TransportException::END_OF_FILE, "No more data available")
107
+ end
108
+
109
+ buf << Bytes.force_binary_encoding(chunk)
88
110
  end
89
111
 
90
112
  buf
@@ -107,7 +129,7 @@ module Thrift
107
129
 
108
130
  class BaseTransportFactory
109
131
  def get_transport(trans)
110
- return trans
132
+ trans
111
133
  end
112
134
 
113
135
  def to_s
@@ -1,4 +1,5 @@
1
1
  # encoding: ascii-8bit
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
@@ -30,7 +31,7 @@ module Thrift
30
31
  end
31
32
 
32
33
  def open?
33
- return @transport.open?
34
+ @transport.open?
34
35
  end
35
36
 
36
37
  def open
@@ -65,7 +66,7 @@ module Thrift
65
66
  # The read buffer has some data now, read a single byte. Using get_string_byte() avoids
66
67
  # allocating a temp string of size 1 unnecessarily.
67
68
  @index += 1
68
- return Bytes.get_string_byte(@rbuf, @index - 1)
69
+ Bytes.get_string_byte(@rbuf, @index - 1)
69
70
  end
70
71
 
71
72
  # Reads a number of bytes from the transport into the buffer passed.
@@ -106,13 +107,13 @@ module Thrift
106
107
  end
107
108
 
108
109
  def to_s
109
- "buffered(#{@transport.to_s})"
110
+ "buffered(#{@transport})"
110
111
  end
111
112
  end
112
113
 
113
114
  class BufferedTransportFactory < BaseTransportFactory
114
115
  def get_transport(transport)
115
- return BufferedTransport.new(transport)
116
+ BufferedTransport.new(transport)
116
117
  end
117
118
 
118
119
  def to_s
@@ -1,4 +1,5 @@
1
1
  # encoding: ascii-8bit
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
@@ -20,15 +21,30 @@
20
21
 
21
22
  module Thrift
22
23
  class FramedTransport < BaseTransport
23
- def initialize(transport, read = true, write = true)
24
+ # The largest frame accepted by default. The value the Java, netstd, C++
25
+ # and Python framed transports use.
26
+ DEFAULT_MAX_FRAME_SIZE = 16_384_000
27
+
28
+ # The frame length field is 30 bits, so no frame can usefully be larger
29
+ # than this whatever a caller configures.
30
+ HARD_MAX_FRAME_SIZE = 0x3FFFFFFF
31
+
32
+ def initialize(transport, read = true, write = true, max_frame_size: DEFAULT_MAX_FRAME_SIZE)
33
+ unless max_frame_size > 0 && max_frame_size <= HARD_MAX_FRAME_SIZE
34
+ raise ArgumentError, "max_frame_size must be > 0 and <= #{HARD_MAX_FRAME_SIZE}"
35
+ end
36
+
24
37
  @transport = transport
25
38
  @rbuf = Bytes.empty_byte_buffer
26
39
  @wbuf = Bytes.empty_byte_buffer
27
40
  @read = read
28
41
  @write = write
29
42
  @index = 0
43
+ @max_frame_size = max_frame_size
30
44
  end
31
45
 
46
+ attr_reader :max_frame_size
47
+
32
48
  def open?
33
49
  @transport.open?
34
50
  end
@@ -53,14 +69,14 @@ module Thrift
53
69
  end
54
70
 
55
71
  def read_byte
56
- return @transport.read_byte() unless @read
72
+ return @transport.read_byte unless @read
57
73
 
58
74
  read_frame if @index >= @rbuf.length
59
75
 
60
76
  # The read buffer has some data now, read a single byte. Using get_string_byte() avoids
61
77
  # allocating a temp string of size 1 unnecessarily.
62
78
  @index += 1
63
- return Bytes.get_string_byte(@rbuf, @index - 1)
79
+ Bytes.get_string_byte(@rbuf, @index - 1)
64
80
  end
65
81
 
66
82
  def read_into_buffer(buffer, size)
@@ -91,7 +107,7 @@ module Thrift
91
107
  def flush
92
108
  return @transport.flush unless @write
93
109
 
94
- out = [@wbuf.length].pack('N')
110
+ out = [@wbuf.length].pack("N")
95
111
  # Array#pack should return a BINARY encoded String, so it shouldn't be necessary to force encoding
96
112
  out << @wbuf
97
113
  @transport.write(out)
@@ -100,13 +116,23 @@ module Thrift
100
116
  end
101
117
 
102
118
  def to_s
103
- "framed(#{@transport.to_s})"
119
+ "framed(#{@transport})"
104
120
  end
105
121
 
106
122
  private
107
123
 
108
124
  def read_frame
109
- sz = @transport.read_all(4).unpack('N').first
125
+ sz = @transport.read_all(4).unpack1("N")
126
+ raise TransportException.new(TransportException::END_OF_FILE, "Cannot read from a zero-length frame") if sz == 0
127
+
128
+ # Checked before the read, not after: read_all accumulates whatever the
129
+ # four bytes declare while the peer decides how much of it to send.
130
+ if sz > @max_frame_size
131
+ raise TransportException.new(
132
+ TransportException::UNKNOWN,
133
+ "Frame size #{sz} exceeds maximum #{@max_frame_size}",
134
+ )
135
+ end
110
136
 
111
137
  @index = 0
112
138
  @rbuf = @transport.read_all(sz)
@@ -115,7 +141,7 @@ module Thrift
115
141
 
116
142
  class FramedTransportFactory < BaseTransportFactory
117
143
  def get_transport(transport)
118
- return FramedTransport.new(transport)
144
+ FramedTransport.new(transport)
119
145
  end
120
146
 
121
147
  def to_s