thrift 0.23.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 (114) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +93 -33
  3. data/benchmark/benchmark.rb +2 -1
  4. data/benchmark/client.rb +1 -0
  5. data/benchmark/server.rb +1 -0
  6. data/benchmark/thin_server.rb +1 -0
  7. data/ext/binary_protocol_accelerated.c +78 -23
  8. data/ext/bytes.c +14 -0
  9. data/ext/compact_protocol.c +59 -31
  10. data/ext/extconf.rb +8 -0
  11. data/ext/memory_buffer.c +37 -0
  12. data/ext/struct.c +14 -14
  13. data/ext/thrift_native.c +1 -0
  14. data/lib/thrift/bytes.rb +4 -1
  15. data/lib/thrift/client.rb +1 -12
  16. data/lib/thrift/exceptions.rb +1 -0
  17. data/lib/thrift/multiplexed_processor.rb +14 -4
  18. data/lib/thrift/processor.rb +1 -0
  19. data/lib/thrift/protocol/base_protocol.rb +15 -6
  20. data/lib/thrift/protocol/binary_protocol.rb +41 -12
  21. data/lib/thrift/protocol/binary_protocol_accelerated.rb +1 -0
  22. data/lib/thrift/protocol/compact_protocol.rb +15 -4
  23. data/lib/thrift/protocol/header_protocol.rb +1 -0
  24. data/lib/thrift/protocol/json_protocol.rb +18 -11
  25. data/lib/thrift/protocol/multiplexed_protocol.rb +1 -0
  26. data/lib/thrift/protocol/protocol_decorator.rb +1 -0
  27. data/lib/thrift/serializer/deserializer.rb +1 -0
  28. data/lib/thrift/serializer/serializer.rb +1 -0
  29. data/lib/thrift/server/base_server.rb +1 -0
  30. data/lib/thrift/server/mongrel_http_server.rb +1 -0
  31. data/lib/thrift/server/nonblocking_server.rb +28 -2
  32. data/lib/thrift/server/simple_server.rb +9 -1
  33. data/lib/thrift/server/thin_http_server.rb +1 -0
  34. data/lib/thrift/server/thread_pool_server.rb +1 -0
  35. data/lib/thrift/server/threaded_server.rb +9 -1
  36. data/lib/thrift/struct.rb +1 -0
  37. data/lib/thrift/struct_union.rb +4 -9
  38. data/lib/thrift/thrift_native.rb +2 -1
  39. data/lib/thrift/transport/base_server_transport.rb +3 -0
  40. data/lib/thrift/transport/base_transport.rb +6 -2
  41. data/lib/thrift/transport/buffered_transport.rb +1 -0
  42. data/lib/thrift/transport/framed_transport.rb +1 -0
  43. data/lib/thrift/transport/header_transport.rb +47 -1
  44. data/lib/thrift/transport/http_client_transport.rb +1 -0
  45. data/lib/thrift/transport/io_stream_transport.rb +1 -0
  46. data/lib/thrift/transport/memory_buffer_transport.rb +7 -0
  47. data/lib/thrift/transport/server_socket.rb +6 -3
  48. data/lib/thrift/transport/socket.rb +68 -29
  49. data/lib/thrift/transport/ssl_server_socket.rb +3 -2
  50. data/lib/thrift/transport/ssl_socket.rb +43 -11
  51. data/lib/thrift/transport/unix_server_socket.rb +5 -1
  52. data/lib/thrift/transport/unix_socket.rb +1 -0
  53. data/lib/thrift/types.rb +1 -0
  54. data/lib/thrift/union.rb +1 -0
  55. data/lib/thrift/uuid.rb +1 -0
  56. data/lib/thrift.rb +1 -0
  57. data/spec/ThriftSpec.thrift +16 -0
  58. data/spec/base_protocol_spec.rb +20 -0
  59. data/spec/base_transport_spec.rb +42 -1
  60. data/spec/binary_protocol_accelerated_spec.rb +1 -0
  61. data/spec/binary_protocol_spec.rb +1 -0
  62. data/spec/binary_protocol_spec_shared.rb +79 -15
  63. data/spec/bytes_spec.rb +19 -1
  64. data/spec/client_spec.rb +1 -0
  65. data/spec/compact_protocol_spec.rb +99 -0
  66. data/spec/constants_demo_spec.rb +1 -0
  67. data/spec/exception_spec.rb +1 -0
  68. data/spec/flat_spec.rb +1 -0
  69. data/spec/header_protocol_spec.rb +1 -0
  70. data/spec/header_transport_spec.rb +45 -0
  71. data/spec/http_client_spec.rb +1 -0
  72. data/spec/json_protocol_spec.rb +26 -4
  73. data/spec/multiplexed_processor_spec.rb +75 -0
  74. data/spec/namespaced_spec.rb +1 -0
  75. data/spec/nonblocking_server_spec.rb +86 -18
  76. data/spec/processor_spec.rb +1 -0
  77. data/spec/recursion_depth_spec.rb +223 -0
  78. data/spec/serializer_spec.rb +1 -0
  79. data/spec/server_socket_spec.rb +37 -0
  80. data/spec/server_spec.rb +52 -0
  81. data/spec/socket_spec.rb +119 -12
  82. data/spec/socket_spec_shared.rb +1 -0
  83. data/spec/spec_helper.rb +1 -0
  84. data/spec/ssl_server_socket_spec.rb +40 -0
  85. data/spec/ssl_socket_spec.rb +171 -10
  86. data/spec/struct_nested_containers_spec.rb +1 -0
  87. data/spec/struct_spec.rb +1 -0
  88. data/spec/support/header_protocol_helper.rb +2 -1
  89. data/spec/thin_http_server_spec.rb +1 -0
  90. data/spec/types_spec.rb +1 -0
  91. data/spec/union_spec.rb +1 -0
  92. data/spec/unix_socket_spec.rb +16 -0
  93. data/spec/uuid_validation_spec.rb +1 -0
  94. data/test/fuzz/Makefile +779 -0
  95. data/test/fuzz/Makefile.in +775 -0
  96. data/test/fuzz/fuzz_common.rb +1 -0
  97. data/test/fuzz/fuzz_parse_binary_protocol.rb +1 -0
  98. data/test/fuzz/fuzz_parse_binary_protocol_accelerated.rb +1 -0
  99. data/test/fuzz/fuzz_parse_binary_protocol_accelerated_harness.rb +1 -0
  100. data/test/fuzz/fuzz_parse_binary_protocol_harness.rb +1 -0
  101. data/test/fuzz/fuzz_parse_compact_protocol.rb +1 -0
  102. data/test/fuzz/fuzz_parse_compact_protocol_harness.rb +1 -0
  103. data/test/fuzz/fuzz_parse_json_protocol.rb +1 -0
  104. data/test/fuzz/fuzz_parse_json_protocol_harness.rb +1 -0
  105. data/test/fuzz/fuzz_roundtrip_binary_protocol.rb +1 -0
  106. data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated.rb +1 -0
  107. data/test/fuzz/fuzz_roundtrip_binary_protocol_accelerated_harness.rb +1 -0
  108. data/test/fuzz/fuzz_roundtrip_binary_protocol_harness.rb +1 -0
  109. data/test/fuzz/fuzz_roundtrip_compact_protocol.rb +1 -0
  110. data/test/fuzz/fuzz_roundtrip_compact_protocol_harness.rb +1 -0
  111. data/test/fuzz/fuzz_roundtrip_json_protocol.rb +1 -0
  112. data/test/fuzz/fuzz_roundtrip_json_protocol_harness.rb +1 -0
  113. data/test/fuzz/fuzz_tracer.rb +1 -0
  114. metadata +12 -46
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97c9f50f4cf15be35eb546b5762d7625e3c2b3f8b97765217962409ba66acb99
4
- data.tar.gz: 92ebb46420c41a9aaa1db0a78cbb7e513d43898c3064283a0b521d4037e63d22
3
+ metadata.gz: 90306a751b8d883922c4556778391d56c8c48fbf008187d3edfe411db4e9f6f7
4
+ data.tar.gz: 95287ba8714521389775a7d2ad265b3ad60a3ca44ada102abcf0c2ee152347cd
5
5
  SHA512:
6
- metadata.gz: 4d5bcb6f79e7625989ff71e4693de7a0ba19babddb01c7727f4820343947af84ee0fef2a916d836c6a8343a4516670fae8af8f7a35bf8b3c94ab001000615baf
7
- data.tar.gz: 6216728eb0196480044992e42c8896f82d6e84314aa530bcc438ee50fa636d690a8bd9196868312cc70263ac3e53f99887001cd3073577573baf63f95142f504
6
+ metadata.gz: d7e052575e5b7e581440a9073d2f4c6ce828b79963f7ce5c077f77e7c759b3589d5671a9e6f37ff4df996061af6b77362cc6248c7d22ac414156594b3fd77339
7
+ data.tar.gz: 35f5ee1d30099e5c70fa49d420153442e2b0b97587a908dea86ad1f9a2bbc44df5ff1baa3474cbc48f4064e20571eab7aa00c5fb155202f05edcd17579cbaddf
data/README.md CHANGED
@@ -47,49 +47,55 @@ clients and services.
47
47
  The Ruby library does not include the Thrift compiler. Use a compiler built
48
48
  from the root of this repository to generate Ruby bindings:
49
49
 
50
- thrift --gen rb path/to/service.thrift
51
- # with namespaced modules
52
- thrift --gen rb:namespaced --recurse path/to/service.thrift
50
+ ```bash
51
+ thrift --gen rb path/to/service.thrift
52
+ # with namespaced modules
53
+ thrift --gen rb:namespaced --recurse path/to/service.thrift
54
+ ```
53
55
 
54
56
  Generated files are typically written to `gen-rb/` and can be required
55
57
  directly from your application.
56
58
 
57
59
  ## Basic Client Usage
58
60
 
59
- $:.push File.expand_path('gen-rb', __dir__)
60
- require 'thrift'
61
- require 'calculator'
61
+ ```ruby
62
+ $:.push File.expand_path('gen-rb', __dir__)
63
+ require 'thrift'
64
+ require 'calculator'
62
65
 
63
- socket = Thrift::Socket.new('localhost', 9090)
64
- transport = Thrift::BufferedTransport.new(socket)
65
- protocol = Thrift::BinaryProtocol.new(transport)
66
- client = Calculator::Client.new(protocol)
66
+ socket = Thrift::Socket.new('localhost', 9090)
67
+ transport = Thrift::BufferedTransport.new(socket)
68
+ protocol = Thrift::BinaryProtocol.new(transport)
69
+ client = Calculator::Client.new(protocol)
67
70
 
68
- transport.open
69
- puts client.add(1, 1)
70
- transport.close
71
+ transport.open
72
+ puts client.add(1, 1)
73
+ transport.close
74
+ ```
71
75
 
72
76
  ## Basic Server Usage
73
77
 
74
- $:.push File.expand_path('gen-rb', __dir__)
75
- require 'thrift'
76
- require 'calculator'
78
+ ```ruby
79
+ $:.push File.expand_path('gen-rb', __dir__)
80
+ require 'thrift'
81
+ require 'calculator'
77
82
 
78
- class CalculatorHandler
79
- def add(a, b)
80
- a + b
81
- end
82
- end
83
+ class CalculatorHandler
84
+ def add(a, b)
85
+ a + b
86
+ end
87
+ end
83
88
 
84
- handler = CalculatorHandler.new
85
- processor = Calculator::Processor.new(handler)
86
- server_transport = Thrift::ServerSocket.new(9090)
87
- transport_factory = Thrift::BufferedTransportFactory.new
88
- protocol_factory = Thrift::BinaryProtocolFactory.new
89
+ handler = CalculatorHandler.new
90
+ processor = Calculator::Processor.new(handler)
91
+ server_transport = Thrift::ServerSocket.new(9090)
92
+ transport_factory = Thrift::BufferedTransportFactory.new
93
+ protocol_factory = Thrift::BinaryProtocolFactory.new
89
94
 
90
- server = Thrift::ThreadedServer.new(processor, server_transport,
91
- transport_factory, protocol_factory)
92
- server.serve
95
+ server = Thrift::ThreadedServer.new(processor, server_transport,
96
+ transport_factory, protocol_factory)
97
+ server.serve
98
+ ```
93
99
 
94
100
  ## Development and Tests
95
101
 
@@ -111,6 +117,52 @@ directly from your application.
111
117
 
112
118
  ## Breaking Changes
113
119
 
120
+ ### 0.24.0
121
+
122
+ Connect timeout handling changed for both `Thrift::Socket` and
123
+ `Thrift::SSLSocket`.
124
+
125
+ - `timeout == nil` and `timeout == 0` now use blocking connect/open semantics.
126
+ Older releases already treated `nil` that way, but treated `0` differently
127
+ across operations: `read` and `write` used the blocking path, plain TCP
128
+ `open` used a zero-length poll, and TLS `open` could spin in the handshake
129
+ retry loop.
130
+ - Positive timeouts now bound the whole connect/open operation instead of
131
+ effectively applying a fresh timeout window at each wait or address attempt.
132
+ For plain TCP this budget is shared across address fallback. For TLS it is
133
+ shared across the TCP connect and the SSL handshake.
134
+ - Connect/open timeout expiry now raises
135
+ `Thrift::TransportException::TIMED_OUT`. Older releases could report the same
136
+ condition as `NOT_OPEN`, and `Thrift::SSLSocket` could keep retrying the
137
+ handshake after the wait timed out.
138
+
139
+ If your application matched `NOT_OPEN` for connect timeout handling, update it
140
+ to handle `TIMED_OUT`. If you relied on `timeout = 0` meaning immediate failure
141
+ or on repeated retries extending the effective timeout during TCP fallback or
142
+ TLS handshake, update those call paths before upgrading.
143
+
144
+ Ruby server socket transports now apply a 5-second timeout to accepted client
145
+ sockets by default. This prevents stalled clients from blocking server threads
146
+ indefinitely during response writes. Applications that intentionally require
147
+ blocking accepted sockets can pass `client_timeout: nil` or `client_timeout: 0`
148
+ when constructing `Thrift::ServerSocket`, `Thrift::SSLServerSocket`, or
149
+ `Thrift::UNIXServerSocket`.
150
+
151
+ Generated Ruby structs and unions now suffix field ID constants as
152
+ `*_FIELD_ID` instead of exposing bare uppercased field names. For example,
153
+ `MyStruct::FOO` becomes `MyStruct::FOO_FIELD_ID`. This avoids collisions with
154
+ the generated `FIELDS` metadata hash for field names such as `fields`, but it
155
+ is a source-compatible break if your application referenced the old constants
156
+ directly. Regenerate Ruby code and update those constant references atomically.
157
+
158
+ Ruby binary protocol writers now enforce signed Thrift integer ranges before
159
+ serializing values. `byte`, `i16`, `i32`, and `i64` writes reject values outside
160
+ their declared signed ranges, and binary/string/container sizes must fit in a
161
+ non-negative signed `i32` length. Older releases could silently wrap or clip
162
+ some out-of-range values, such as writing `255` as a byte that read back as
163
+ `-1`. This applies to both `Thrift::BinaryProtocol` and
164
+ `Thrift::BinaryProtocolAccelerated`.
165
+
114
166
  ### 0.23.0
115
167
 
116
168
  The documented source-build flow now effectively requires Ruby `2.7+`.
@@ -157,14 +209,22 @@ best-effort, not guaranteed.
157
209
  `thrift --gen rb` and namespaced output from `thrift --gen rb:namespaced`
158
210
  use different require paths, so switch them atomically with regenerated code.
159
211
 
160
- # --gen rb
161
- require 'calculator'
212
+ ```ruby
213
+ # --gen rb
214
+ require 'calculator'
162
215
 
163
- # --gen rb:namespaced
164
- require 'my_namespace/calculator'
216
+ # --gen rb:namespaced
217
+ require 'my_namespace/calculator'
218
+ ```
165
219
 
166
220
  ## Migration Notes
167
221
 
222
+ - If you upgrade to `0.24.0`, treat `timeout` on `Thrift::Socket` and
223
+ `Thrift::SSLSocket` as one budget for the whole open path. For
224
+ `Thrift::SSLSocket`, that includes both the TCP connect and the TLS
225
+ handshake.
226
+ - If you upgrade to `0.24.0`, handle connect/open timeout expiry as
227
+ `Thrift::TransportException::TIMED_OUT` instead of `NOT_OPEN`.
168
228
  - If you upgrade across the stricter reply-validation changes, regenerate all
169
229
  Ruby stubs and deploy them with the matching Ruby runtime. Do not mix old
170
230
  generated code, new generated code, and new runtime code on the same client
@@ -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
@@ -116,7 +117,7 @@ class BenchmarkManager
116
117
  def collect_output
117
118
  puts "Collecting output..."
118
119
  # read from @pool until all sockets are closed
119
- @buffers = Hash.new { |h, k| h[k] = '' }
120
+ @buffers = Hash.new { |h, k| h[k] = ''.b }
120
121
  until @pool.empty?
121
122
  rd, = select(@pool)
122
123
  next if rd.nil?
data/benchmark/client.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
data/benchmark/server.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
@@ -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
@@ -31,13 +31,59 @@ VALUE rb_thrift_binary_proto_native_qmark(VALUE self) {
31
31
  return Qtrue;
32
32
  }
33
33
 
34
-
35
-
36
34
  static int VERSION_1;
37
35
  static int VERSION_MASK;
38
36
  static int TYPE_MASK;
39
37
  static ID rbuf_ivar_id;
40
38
 
39
+ static int64_t checked_integer_value(VALUE value) {
40
+ if (!RB_INTEGER_TYPE_P(value)) {
41
+ rb_raise(rb_eTypeError, "integer argument expected");
42
+ }
43
+
44
+ return NUM2LL(value);
45
+ }
46
+
47
+ static int64_t checked_integer_range(VALUE value, int64_t min, int64_t max) {
48
+ int64_t integer = checked_integer_value(value);
49
+
50
+ if (integer < min || integer > max) {
51
+ rb_raise(rb_eRangeError, "integer out of bounds");
52
+ }
53
+
54
+ return integer;
55
+ }
56
+
57
+ static int8_t checked_byte_value(VALUE value) {
58
+ return (int8_t)checked_integer_range(value, INT8_MIN, INT8_MAX);
59
+ }
60
+
61
+ static int16_t checked_i16_value(VALUE value) {
62
+ return (int16_t)checked_integer_range(value, INT16_MIN, INT16_MAX);
63
+ }
64
+
65
+ static int32_t checked_i32_value(VALUE value) {
66
+ return (int32_t)checked_integer_range(value, INT32_MIN, INT32_MAX);
67
+ }
68
+
69
+ static int32_t checked_size_value(VALUE value) {
70
+ return (int32_t)checked_integer_range(value, 0, INT32_MAX);
71
+ }
72
+
73
+ static int64_t checked_i64_value(VALUE value) {
74
+ return checked_integer_range(value, INT64_MIN, INT64_MAX);
75
+ }
76
+
77
+ static int32_t checked_string_length(VALUE str) {
78
+ long length = RSTRING_LEN(str);
79
+
80
+ if (length > INT32_MAX) {
81
+ rb_raise(rb_eRangeError, "string too long");
82
+ }
83
+
84
+ return (int32_t)length;
85
+ }
86
+
41
87
  static void write_byte_direct(VALUE trans, int8_t b) {
42
88
  WRITE(trans, (char*)&b, 1);
43
89
  }
@@ -83,7 +129,7 @@ static void write_string_direct(VALUE trans, VALUE str) {
83
129
  rb_raise(rb_eStandardError, "Value should be a string");
84
130
  }
85
131
  str = convert_to_utf8_byte_buffer(str);
86
- write_i32_direct(trans, (int32_t)RSTRING_LEN(str));
132
+ write_i32_direct(trans, checked_string_length(str));
87
133
  rb_funcall(trans, write_method_id, 1, str);
88
134
  }
89
135
 
@@ -124,13 +170,14 @@ VALUE rb_thrift_binary_proto_write_message_begin(VALUE self, VALUE name, VALUE t
124
170
  VALUE strict_write = GET_STRICT_WRITE(self);
125
171
 
126
172
  if (strict_write == Qtrue) {
127
- write_i32_direct(trans, VERSION_1 | FIX2INT(type));
173
+ int8_t message_type = checked_byte_value(type);
174
+ write_i32_direct(trans, VERSION_1 | message_type);
128
175
  write_string_direct(trans, name);
129
- write_i32_direct(trans, FIX2INT(seqid));
176
+ write_i32_direct(trans, checked_i32_value(seqid));
130
177
  } else {
131
178
  write_string_direct(trans, name);
132
- write_byte_direct(trans, FIX2INT(type));
133
- write_i32_direct(trans, FIX2INT(seqid));
179
+ write_byte_direct(trans, checked_byte_value(type));
180
+ write_i32_direct(trans, checked_i32_value(seqid));
134
181
  }
135
182
 
136
183
  return Qnil;
@@ -138,8 +185,8 @@ VALUE rb_thrift_binary_proto_write_message_begin(VALUE self, VALUE name, VALUE t
138
185
 
139
186
  VALUE rb_thrift_binary_proto_write_field_begin(VALUE self, VALUE name, VALUE type, VALUE id) {
140
187
  VALUE trans = GET_TRANSPORT(self);
141
- write_byte_direct(trans, FIX2INT(type));
142
- write_i16_direct(trans, FIX2INT(id));
188
+ write_byte_direct(trans, checked_byte_value(type));
189
+ write_i16_direct(trans, checked_i16_value(id));
143
190
 
144
191
  return Qnil;
145
192
  }
@@ -151,17 +198,17 @@ VALUE rb_thrift_binary_proto_write_field_stop(VALUE self) {
151
198
 
152
199
  VALUE rb_thrift_binary_proto_write_map_begin(VALUE self, VALUE ktype, VALUE vtype, VALUE size) {
153
200
  VALUE trans = GET_TRANSPORT(self);
154
- write_byte_direct(trans, FIX2INT(ktype));
155
- write_byte_direct(trans, FIX2INT(vtype));
156
- write_i32_direct(trans, FIX2INT(size));
201
+ write_byte_direct(trans, checked_byte_value(ktype));
202
+ write_byte_direct(trans, checked_byte_value(vtype));
203
+ write_i32_direct(trans, checked_size_value(size));
157
204
 
158
205
  return Qnil;
159
206
  }
160
207
 
161
208
  VALUE rb_thrift_binary_proto_write_list_begin(VALUE self, VALUE etype, VALUE size) {
162
209
  VALUE trans = GET_TRANSPORT(self);
163
- write_byte_direct(trans, FIX2INT(etype));
164
- write_i32_direct(trans, FIX2INT(size));
210
+ write_byte_direct(trans, checked_byte_value(etype));
211
+ write_i32_direct(trans, checked_size_value(size));
165
212
 
166
213
  return Qnil;
167
214
  }
@@ -178,25 +225,25 @@ VALUE rb_thrift_binary_proto_write_bool(VALUE self, VALUE b) {
178
225
 
179
226
  VALUE rb_thrift_binary_proto_write_byte(VALUE self, VALUE byte) {
180
227
  CHECK_NIL(byte);
181
- write_byte_direct(GET_TRANSPORT(self), NUM2INT(byte));
228
+ write_byte_direct(GET_TRANSPORT(self), checked_byte_value(byte));
182
229
  return Qnil;
183
230
  }
184
231
 
185
232
  VALUE rb_thrift_binary_proto_write_i16(VALUE self, VALUE i16) {
186
233
  CHECK_NIL(i16);
187
- write_i16_direct(GET_TRANSPORT(self), FIX2INT(i16));
234
+ write_i16_direct(GET_TRANSPORT(self), checked_i16_value(i16));
188
235
  return Qnil;
189
236
  }
190
237
 
191
238
  VALUE rb_thrift_binary_proto_write_i32(VALUE self, VALUE i32) {
192
239
  CHECK_NIL(i32);
193
- write_i32_direct(GET_TRANSPORT(self), NUM2INT(i32));
240
+ write_i32_direct(GET_TRANSPORT(self), checked_i32_value(i32));
194
241
  return Qnil;
195
242
  }
196
243
 
197
244
  VALUE rb_thrift_binary_proto_write_i64(VALUE self, VALUE i64) {
198
245
  CHECK_NIL(i64);
199
- write_i64_direct(GET_TRANSPORT(self), NUM2LL(i64));
246
+ write_i64_direct(GET_TRANSPORT(self), checked_i64_value(i64));
200
247
  return Qnil;
201
248
  }
202
249
 
@@ -224,7 +271,7 @@ VALUE rb_thrift_binary_proto_write_binary(VALUE self, VALUE buf) {
224
271
  CHECK_NIL(buf);
225
272
  VALUE trans = GET_TRANSPORT(self);
226
273
  buf = force_binary_encoding(buf);
227
- write_i32_direct(trans, (int32_t)RSTRING_LEN(buf));
274
+ write_i32_direct(trans, checked_string_length(buf));
228
275
  rb_funcall(trans, write_method_id, 1, buf);
229
276
  return Qnil;
230
277
  }
@@ -377,17 +424,24 @@ VALUE rb_thrift_binary_proto_read_field_begin(VALUE self) {
377
424
  }
378
425
  }
379
426
 
427
+ #define CHECK_NEGATIVE_SIZE(size) \
428
+ if (RB_UNLIKELY((size) < 0)) { \
429
+ rb_exc_raise(get_protocol_exception(INT2FIX(PROTOERR_NEGATIVE_SIZE), rb_str_new2("Negative size"))); \
430
+ }
431
+
380
432
  VALUE rb_thrift_binary_proto_read_map_begin(VALUE self) {
381
433
  VALUE ktype = rb_thrift_binary_proto_read_byte(self);
382
434
  VALUE vtype = rb_thrift_binary_proto_read_byte(self);
383
- VALUE size = rb_thrift_binary_proto_read_i32(self);
384
- return rb_ary_new3(3, ktype, vtype, size);
435
+ int size = read_i32_direct(self);
436
+ CHECK_NEGATIVE_SIZE(size);
437
+ return rb_ary_new3(3, ktype, vtype, INT2NUM(size));
385
438
  }
386
439
 
387
440
  VALUE rb_thrift_binary_proto_read_list_begin(VALUE self) {
388
441
  VALUE etype = rb_thrift_binary_proto_read_byte(self);
389
- VALUE size = rb_thrift_binary_proto_read_i32(self);
390
- return rb_ary_new3(2, etype, size);
442
+ int size = read_i32_direct(self);
443
+ CHECK_NEGATIVE_SIZE(size);
444
+ return rb_ary_new3(2, etype, INT2NUM(size));
391
445
  }
392
446
 
393
447
  VALUE rb_thrift_binary_proto_read_set_begin(VALUE self) {
@@ -431,6 +485,7 @@ VALUE rb_thrift_binary_proto_read_string(VALUE self) {
431
485
 
432
486
  VALUE rb_thrift_binary_proto_read_binary(VALUE self) {
433
487
  int size = read_i32_direct(self);
488
+ CHECK_NEGATIVE_SIZE(size);
434
489
  return READ(self, size);
435
490
  }
436
491
 
data/ext/bytes.c CHANGED
@@ -24,6 +24,20 @@
24
24
  #include <constants.h>
25
25
 
26
26
  VALUE force_binary_encoding(VALUE buffer) {
27
+ if (RB_TYPE_P(buffer, T_STRING)) {
28
+ if (rb_enc_get_index(buffer) == rb_ascii8bit_encindex()) {
29
+ return buffer;
30
+ }
31
+
32
+ if (RB_OBJ_FROZEN(buffer)) {
33
+ buffer = rb_obj_dup(buffer);
34
+ }
35
+
36
+ rb_enc_associate_index(buffer, rb_ascii8bit_encindex());
37
+
38
+ return buffer;
39
+ }
40
+
27
41
  return rb_funcall(thrift_bytes_module, force_binary_encoding_id, 1, buffer);
28
42
  }
29
43
 
@@ -124,21 +124,29 @@ static void write_field_begin_internal(VALUE self, VALUE type, VALUE id_value, V
124
124
  SET_LAST_ID(self, id_value);
125
125
  }
126
126
 
127
- static int32_t int_to_zig_zag(int32_t n) {
128
- return (n << 1) ^ (n >> 31);
127
+ static uint32_t int_to_zig_zag(int32_t n) {
128
+ return (((uint32_t)n) << 1) ^ (0U - (uint32_t)(n < 0));
129
129
  }
130
130
 
131
131
  static uint64_t ll_to_zig_zag(int64_t n) {
132
- return (n << 1) ^ (n >> 63);
132
+ return (((uint64_t)n) << 1) ^ (0ULL - (uint64_t)(n < 0));
133
+ }
134
+
135
+ static uint32_t message_seqid_to_varint32(int32_t seqid) {
136
+ return seqid < 0 ? (uint32_t)((int64_t)seqid + (INT64_C(1) << 32)) : (uint32_t)seqid;
137
+ }
138
+
139
+ static int32_t message_seqid_from_varint32(uint32_t seqid) {
140
+ return seqid > INT32_MAX ? (int32_t)((int64_t)seqid - (INT64_C(1) << 32)) : (int32_t)seqid;
133
141
  }
134
142
 
135
143
  static void write_varint32(VALUE transport, uint32_t n) {
136
144
  while (true) {
137
- if ((n & ~0x7F) == 0) {
138
- write_byte_direct(transport, n & 0x7f);
145
+ if ((n & ~0x7FU) == 0U) {
146
+ write_byte_direct(transport, n & 0x7FU);
139
147
  break;
140
148
  } else {
141
- write_byte_direct(transport, (n & 0x7F) | 0x80);
149
+ write_byte_direct(transport, (n & 0x7FU) | 0x80U);
142
150
  n = n >> 7;
143
151
  }
144
152
  }
@@ -146,11 +154,11 @@ static void write_varint32(VALUE transport, uint32_t n) {
146
154
 
147
155
  static void write_varint64(VALUE transport, uint64_t n) {
148
156
  while (true) {
149
- if ((n & ~0x7F) == 0) {
150
- write_byte_direct(transport, n & 0x7f);
157
+ if ((n & ~0x7FULL) == 0ULL) {
158
+ write_byte_direct(transport, n & 0x7FULL);
151
159
  break;
152
160
  } else {
153
- write_byte_direct(transport, (n & 0x7F) | 0x80);
161
+ write_byte_direct(transport, (n & 0x7FULL) | 0x80ULL);
154
162
  n = n >> 7;
155
163
  }
156
164
  }
@@ -208,9 +216,10 @@ VALUE rb_thrift_compact_proto_write_set_end(VALUE self) {
208
216
 
209
217
  VALUE rb_thrift_compact_proto_write_message_begin(VALUE self, VALUE name, VALUE type, VALUE seqid) {
210
218
  VALUE transport = GET_TRANSPORT(self);
219
+ int32_t seqid_value = FIX2INT(seqid);
211
220
  write_byte_direct(transport, PROTOCOL_ID);
212
221
  write_byte_direct(transport, (VERSION & VERSION_MASK) | ((FIX2INT(type) << TYPE_SHIFT_AMOUNT) & TYPE_MASK));
213
- write_varint32(transport, FIX2INT(seqid));
222
+ write_varint32(transport, message_seqid_to_varint32(seqid_value));
214
223
  rb_thrift_compact_proto_write_string(self, name);
215
224
 
216
225
  return Qnil;
@@ -419,30 +428,49 @@ static char read_byte_direct(VALUE self) {
419
428
  return (char)(FIX2INT(byte));
420
429
  }
421
430
 
422
- static int64_t zig_zag_to_ll(int64_t n) {
423
- return (((uint64_t)n) >> 1) ^ -(n & 1);
431
+ static int64_t zig_zag_to_ll(uint64_t n) {
432
+ return (int64_t)((n >> 1) ^ (0ULL - (n & 1ULL)));
424
433
  }
425
434
 
426
- static int32_t zig_zag_to_int(int32_t n) {
427
- return (((uint32_t)n) >> 1) ^ -(n & 1);
435
+ static int32_t zig_zag_to_int(uint32_t n) {
436
+ return (int32_t)((n >> 1) ^ (0U - (n & 1U)));
428
437
  }
429
438
 
430
- static int64_t read_varint64(VALUE self) {
431
- int shift = 0;
432
- int64_t result = 0;
433
- while (true) {
439
+ #define MAX_VARINT32_BYTES 5 /* ceil(32/7); matches protobuf wire format */
440
+ #define MAX_VARINT64_BYTES 10 /* ceil(64/7); matches protobuf wire format */
441
+
442
+ static uint64_t read_varint64(VALUE self) {
443
+ int i, shift = 0;
444
+ uint64_t result = 0;
445
+ for (i = 0; i < MAX_VARINT64_BYTES; i++) {
434
446
  int8_t b = read_byte_direct(self);
435
- result = result | ((uint64_t)(b & 0x7f) << shift);
447
+ result |= ((uint64_t)(b & 0x7f) << shift);
436
448
  if ((b & 0x80) != 0x80) {
437
- break;
449
+ return result;
450
+ }
451
+ shift += 7;
452
+ }
453
+ rb_exc_raise(get_protocol_exception(INT2FIX(PROTOERR_INVALID_DATA), rb_str_new2("Variable-length int over 10 bytes.")));
454
+ return 0; /* unreachable */
455
+ }
456
+
457
+ static uint32_t read_varint32(VALUE self) {
458
+ int i, shift = 0;
459
+ uint32_t result = 0;
460
+ for (i = 0; i < MAX_VARINT32_BYTES; i++) {
461
+ int8_t b = read_byte_direct(self);
462
+ result |= ((uint32_t)(b & 0x7f) << shift);
463
+ if ((b & 0x80) != 0x80) {
464
+ return result;
438
465
  }
439
466
  shift += 7;
440
467
  }
441
- return result;
468
+ rb_exc_raise(get_protocol_exception(INT2FIX(PROTOERR_INVALID_DATA), rb_str_new2("Variable-length int over 5 bytes.")));
469
+ return 0; /* unreachable */
442
470
  }
443
471
 
444
472
  static int16_t read_i16(VALUE self) {
445
- return zig_zag_to_int((int32_t)read_varint64(self));
473
+ return (int16_t)zig_zag_to_int(read_varint32(self));
446
474
  }
447
475
 
448
476
  VALUE rb_thrift_compact_proto_read_message_end(VALUE self) {
@@ -494,7 +522,7 @@ VALUE rb_thrift_compact_proto_read_message_begin(VALUE self) {
494
522
  }
495
523
 
496
524
  int8_t type = (version_and_type >> TYPE_SHIFT_AMOUNT) & TYPE_BITS;
497
- int32_t seqid = (int32_t)read_varint64(self);
525
+ int32_t seqid = message_seqid_from_varint32(read_varint32(self));
498
526
  VALUE messageName = rb_thrift_compact_proto_read_string(self);
499
527
  return rb_ary_new3(3, messageName, INT2FIX(type), INT2NUM(seqid));
500
528
  }
@@ -532,19 +560,19 @@ VALUE rb_thrift_compact_proto_read_field_begin(VALUE self) {
532
560
  }
533
561
 
534
562
  VALUE rb_thrift_compact_proto_read_map_begin(VALUE self) {
535
- int32_t size = (int32_t)read_varint64(self);
563
+ uint32_t size = read_varint32(self);
536
564
  uint8_t key_and_value_type = size == 0 ? 0 : read_byte_direct(self);
537
- return rb_ary_new3(3, INT2FIX(get_ttype(key_and_value_type >> 4)), INT2FIX(get_ttype(key_and_value_type & 0xf)), INT2FIX(size));
565
+ return rb_ary_new3(3, INT2FIX(get_ttype(key_and_value_type >> 4)), INT2FIX(get_ttype(key_and_value_type & 0xf)), UINT2NUM(size));
538
566
  }
539
567
 
540
568
  VALUE rb_thrift_compact_proto_read_list_begin(VALUE self) {
541
569
  uint8_t size_and_type = read_byte_direct(self);
542
- int32_t size = (size_and_type >> 4) & 0x0f;
570
+ uint32_t size = (size_and_type >> 4) & 0x0f;
543
571
  if (size == 15) {
544
- size = (int32_t)read_varint64(self);
572
+ size = read_varint32(self);
545
573
  }
546
574
  uint8_t type = get_ttype(size_and_type & 0x0f);
547
- return rb_ary_new3(2, INT2FIX(type), INT2FIX(size));
575
+ return rb_ary_new3(2, INT2FIX(type), UINT2NUM(size));
548
576
  }
549
577
 
550
578
  VALUE rb_thrift_compact_proto_read_set_begin(VALUE self) {
@@ -570,7 +598,7 @@ VALUE rb_thrift_compact_proto_read_i16(VALUE self) {
570
598
  }
571
599
 
572
600
  VALUE rb_thrift_compact_proto_read_i32(VALUE self) {
573
- return INT2NUM(zig_zag_to_int((int32_t)read_varint64(self)));
601
+ return INT2NUM(zig_zag_to_int(read_varint32(self)));
574
602
  }
575
603
 
576
604
  VALUE rb_thrift_compact_proto_read_i64(VALUE self) {
@@ -603,8 +631,8 @@ VALUE rb_thrift_compact_proto_read_string(VALUE self) {
603
631
  }
604
632
 
605
633
  VALUE rb_thrift_compact_proto_read_binary(VALUE self) {
606
- int64_t size = read_varint64(self);
607
- return READ(self, size);
634
+ uint32_t size = read_varint32(self);
635
+ return rb_funcall(GET_TRANSPORT(self), read_all_method_id, 1, UINT2NUM(size));
608
636
  }
609
637
 
610
638
  VALUE rb_thrift_compact_proto_read_uuid(VALUE self) {
data/ext/extconf.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
@@ -24,6 +25,13 @@ else
24
25
 
25
26
  append_cflags(["-fsigned-char", "-g", "-O2", "-Wall", "-Werror", "-Werror=old-style-definition"])
26
27
 
28
+ # clang 21+ introduced -Wdefault-const-init-field-unsafe, which fires on
29
+ # Ruby 3.2's rstring.h (struct RString has a const field via RBasic).
30
+ # This is a Ruby header issue, not a Thrift bug, so suppress the warning here.
31
+ # append_cflags silently ignores flags unsupported by the compiler, so this
32
+ # is safe across all clang versions.
33
+ append_cflags("-Wno-default-const-init-field-unsafe")
34
+
27
35
  # Makes all symbols private by default to avoid unintended conflict
28
36
  # with other gems. To explicitly export symbols you can use RUBY_FUNC_EXPORTED
29
37
  # selectively, or entirely remove this flag.