google-protobuf 3.14.0 → 4.26.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/ext/google/protobuf_c/Rakefile +3 -0
  3. data/ext/google/protobuf_c/convert.c +317 -0
  4. data/ext/google/protobuf_c/convert.h +50 -0
  5. data/ext/google/protobuf_c/defs.c +759 -1709
  6. data/ext/google/protobuf_c/defs.h +82 -0
  7. data/ext/google/protobuf_c/extconf.rb +15 -8
  8. data/ext/google/protobuf_c/glue.c +56 -0
  9. data/ext/google/protobuf_c/map.c +328 -485
  10. data/ext/google/protobuf_c/map.h +44 -0
  11. data/ext/google/protobuf_c/message.c +1061 -530
  12. data/ext/google/protobuf_c/message.h +86 -0
  13. data/ext/google/protobuf_c/protobuf.c +314 -94
  14. data/ext/google/protobuf_c/protobuf.h +66 -621
  15. data/ext/google/protobuf_c/repeated_field.c +314 -353
  16. data/ext/google/protobuf_c/repeated_field.h +41 -0
  17. data/ext/google/protobuf_c/ruby-upb.c +15407 -0
  18. data/ext/google/protobuf_c/ruby-upb.h +13966 -0
  19. data/ext/google/protobuf_c/shared_convert.c +66 -0
  20. data/ext/google/protobuf_c/shared_convert.h +26 -0
  21. data/ext/google/protobuf_c/shared_message.c +67 -0
  22. data/ext/google/protobuf_c/shared_message.h +25 -0
  23. data/ext/google/protobuf_c/third_party/utf8_range/LICENSE +22 -0
  24. data/ext/google/protobuf_c/third_party/utf8_range/utf8_range.c +467 -0
  25. data/ext/google/protobuf_c/third_party/utf8_range/utf8_range.h +22 -0
  26. data/ext/google/protobuf_c/wrap_memcpy.c +7 -29
  27. data/lib/google/protobuf/any_pb.rb +6 -8
  28. data/lib/google/protobuf/api_pb.rb +7 -26
  29. data/lib/google/protobuf/descriptor_pb.rb +65 -0
  30. data/lib/google/protobuf/duration_pb.rb +6 -8
  31. data/lib/google/protobuf/empty_pb.rb +6 -6
  32. data/lib/google/protobuf/ffi/descriptor.rb +164 -0
  33. data/lib/google/protobuf/ffi/descriptor_pool.rb +75 -0
  34. data/lib/google/protobuf/ffi/enum_descriptor.rb +171 -0
  35. data/lib/google/protobuf/ffi/ffi.rb +215 -0
  36. data/lib/google/protobuf/ffi/field_descriptor.rb +328 -0
  37. data/lib/google/protobuf/ffi/file_descriptor.rb +47 -0
  38. data/lib/google/protobuf/ffi/internal/arena.rb +66 -0
  39. data/lib/google/protobuf/ffi/internal/convert.rb +289 -0
  40. data/lib/google/protobuf/ffi/internal/pointer_helper.rb +35 -0
  41. data/lib/google/protobuf/ffi/internal/type_safety.rb +25 -0
  42. data/lib/google/protobuf/ffi/map.rb +409 -0
  43. data/lib/google/protobuf/ffi/message.rb +659 -0
  44. data/lib/google/protobuf/ffi/object_cache.rb +30 -0
  45. data/lib/google/protobuf/ffi/oneof_descriptor.rb +95 -0
  46. data/lib/google/protobuf/ffi/repeated_field.rb +385 -0
  47. data/lib/google/protobuf/field_mask_pb.rb +6 -7
  48. data/lib/google/protobuf/internal/object_cache.rb +99 -0
  49. data/lib/google/protobuf/message_exts.rb +10 -28
  50. data/lib/google/protobuf/plugin_pb.rb +25 -0
  51. data/lib/google/protobuf/repeated_field.rb +19 -30
  52. data/lib/google/protobuf/source_context_pb.rb +6 -7
  53. data/lib/google/protobuf/struct_pb.rb +6 -23
  54. data/lib/google/protobuf/timestamp_pb.rb +6 -8
  55. data/lib/google/protobuf/type_pb.rb +7 -71
  56. data/lib/google/protobuf/well_known_types.rb +17 -36
  57. data/lib/google/protobuf/wrappers_pb.rb +6 -31
  58. data/lib/google/protobuf.rb +32 -118
  59. data/lib/google/protobuf_ffi.rb +49 -0
  60. data/lib/google/protobuf_native.rb +19 -0
  61. data/lib/google/tasks/ffi.rake +100 -0
  62. metadata +88 -37
  63. data/ext/google/protobuf_c/encode_decode.c +0 -1795
  64. data/ext/google/protobuf_c/storage.c +0 -1198
  65. data/ext/google/protobuf_c/upb.c +0 -13817
  66. data/ext/google/protobuf_c/upb.h +0 -6777
  67. data/tests/basic.rb +0 -543
  68. data/tests/generated_code_test.rb +0 -23
  69. data/tests/stress.rb +0 -38
@@ -0,0 +1,659 @@
1
+ # Protocol Buffers - Google's data interchange format
2
+ # Copyright 2023 Google Inc. All rights reserved.
3
+ #
4
+ # Use of this source code is governed by a BSD-style
5
+ # license that can be found in the LICENSE file or at
6
+ # https://developers.google.com/open-source/licenses/bsd
7
+
8
+
9
+ # Decorates Descriptor with the `build_message_class` method that defines
10
+ # Message classes.
11
+ module Google
12
+ module Protobuf
13
+ class FFI
14
+ # Message
15
+ attach_function :clear_message_field, :upb_Message_ClearFieldByDef, [:Message, FieldDescriptor], :void
16
+ attach_function :get_message_value, :upb_Message_GetFieldByDef, [:Message, FieldDescriptor], MessageValue.by_value
17
+ attach_function :get_message_has, :upb_Message_HasFieldByDef, [:Message, FieldDescriptor], :bool
18
+ attach_function :set_message_field, :upb_Message_SetFieldByDef, [:Message, FieldDescriptor, MessageValue.by_value, Internal::Arena], :bool
19
+ attach_function :encode_message, :upb_Encode, [:Message, MiniTable.by_ref, :size_t, Internal::Arena, :pointer, :pointer], EncodeStatus
20
+ attach_function :json_decode_message, :upb_JsonDecode, [:binary_string, :size_t, :Message, Descriptor, :DefPool, :int, Internal::Arena, Status.by_ref], :bool
21
+ attach_function :json_encode_message, :upb_JsonEncode, [:Message, Descriptor, :DefPool, :int, :binary_string, :size_t, Status.by_ref], :size_t
22
+ attach_function :decode_message, :upb_Decode, [:binary_string, :size_t, :Message, MiniTable.by_ref, :ExtensionRegistry, :int, Internal::Arena], DecodeStatus
23
+ attach_function :get_mutable_message, :upb_Message_Mutable, [:Message, FieldDescriptor, Internal::Arena], MutableMessageValue.by_value
24
+ attach_function :get_message_which_oneof, :upb_Message_WhichOneof, [:Message, OneofDescriptor], FieldDescriptor
25
+ attach_function :message_discard_unknown, :upb_Message_DiscardUnknown, [:Message, Descriptor, :int], :bool
26
+ attach_function :message_next, :upb_Message_Next, [:Message, Descriptor, :DefPool, :FieldDefPointer, MessageValue.by_ref, :pointer], :bool
27
+ # MessageValue
28
+ attach_function :message_value_equal, :shared_Msgval_IsEqual, [MessageValue.by_value, MessageValue.by_value, CType, Descriptor], :bool
29
+ attach_function :message_value_hash, :shared_Msgval_GetHash, [MessageValue.by_value, CType, Descriptor, :uint64_t], :uint64_t
30
+ end
31
+
32
+ class Descriptor
33
+ def build_message_class
34
+ descriptor = self
35
+ Class.new(Google::Protobuf::const_get(:AbstractMessage)) do
36
+ @descriptor = descriptor
37
+ class << self
38
+ attr_accessor :descriptor
39
+ private
40
+ attr_accessor :oneof_field_names
41
+ include ::Google::Protobuf::Internal::Convert
42
+ end
43
+
44
+ alias original_method_missing method_missing
45
+ def method_missing(method_name, *args)
46
+ method_missing_internal method_name, *args, mode: :method_missing
47
+ end
48
+
49
+ def respond_to_missing?(method_name, include_private = false)
50
+ method_missing_internal(method_name, mode: :respond_to_missing?) || super
51
+ end
52
+
53
+ ##
54
+ # Public constructor. Automatically allocates from a new Arena.
55
+ def self.new(initial_value = nil)
56
+ instance = allocate
57
+ instance.send(:initialize, initial_value)
58
+ instance
59
+ end
60
+
61
+ def freeze
62
+ return self if frozen?
63
+ super
64
+ @arena.pin self
65
+ self.class.descriptor.each do |field_descriptor|
66
+ next if field_descriptor.has_presence? && !Google::Protobuf::FFI.get_message_has(@msg, field_descriptor)
67
+ if field_descriptor.map? or field_descriptor.repeated? or field_descriptor.sub_message?
68
+ get_field(field_descriptor).freeze
69
+ end
70
+ end
71
+ self
72
+ end
73
+
74
+ def dup
75
+ duplicate = self.class.private_constructor(@arena)
76
+ mini_table = Google::Protobuf::FFI.get_mini_table(self.class.descriptor)
77
+ size = mini_table[:size]
78
+ duplicate.instance_variable_get(:@msg).write_string_length(@msg.read_string_length(size), size)
79
+ duplicate
80
+ end
81
+ alias clone dup
82
+
83
+ def eql?(other)
84
+ return false unless self.class === other
85
+ encoding_options = Google::Protobuf::FFI::Upb_Encode_Deterministic | Google::Protobuf::FFI::Upb_Encode_SkipUnknown
86
+ temporary_arena = Google::Protobuf::FFI.create_arena
87
+ mini_table = Google::Protobuf::FFI.get_mini_table(self.class.descriptor)
88
+ size_one = ::FFI::MemoryPointer.new(:size_t, 1)
89
+ encoding_one = ::FFI::MemoryPointer.new(:pointer, 1)
90
+ encoding_status = Google::Protobuf::FFI.encode_message(@msg, mini_table, encoding_options, temporary_arena, encoding_one.to_ptr, size_one)
91
+ raise ParseError.new "Error comparing messages due to #{encoding_status} while encoding LHS of `eql?()`" unless encoding_status == :Ok
92
+
93
+ size_two = ::FFI::MemoryPointer.new(:size_t, 1)
94
+ encoding_two = ::FFI::MemoryPointer.new(:pointer, 1)
95
+ encoding_status = Google::Protobuf::FFI.encode_message(other.instance_variable_get(:@msg), mini_table, encoding_options, temporary_arena, encoding_two.to_ptr, size_two)
96
+ raise ParseError.new "Error comparing messages due to #{encoding_status} while encoding RHS of `eql?()`" unless encoding_status == :Ok
97
+
98
+ if encoding_one.null? or encoding_two.null?
99
+ raise ParseError.new "Error comparing messages"
100
+ end
101
+ size_one.read(:size_t) == size_two.read(:size_t) and Google::Protobuf::FFI.memcmp(encoding_one.read(:pointer), encoding_two.read(:pointer), size_one.read(:size_t)).zero?
102
+ end
103
+ alias == eql?
104
+
105
+ def hash
106
+ encoding_options = Google::Protobuf::FFI::Upb_Encode_Deterministic | Google::Protobuf::FFI::Upb_Encode_SkipUnknown
107
+ temporary_arena = Google::Protobuf::FFI.create_arena
108
+ mini_table_ptr = Google::Protobuf::FFI.get_mini_table(self.class.descriptor)
109
+ size_ptr = ::FFI::MemoryPointer.new(:size_t, 1)
110
+ encoding = ::FFI::MemoryPointer.new(:pointer, 1)
111
+ encoding_status = Google::Protobuf::FFI.encode_message(@msg, mini_table_ptr, encoding_options, temporary_arena, encoding.to_ptr, size_ptr)
112
+ if encoding_status != :Ok or encoding.null?
113
+ raise ParseError.new "Error calculating hash"
114
+ end
115
+ encoding.read(:pointer).read_string(size_ptr.read(:size_t)).hash
116
+ end
117
+
118
+ def to_h
119
+ to_h_internal @msg, self.class.descriptor
120
+ end
121
+
122
+ ##
123
+ # call-seq:
124
+ # Message.inspect => string
125
+ #
126
+ # Returns a human-readable string representing this message. It will be
127
+ # formatted as "<MessageType: field1: value1, field2: value2, ...>". Each
128
+ # field's value is represented according to its own #inspect method.
129
+ def inspect
130
+ self.class.inspect_internal @msg
131
+ end
132
+
133
+ def to_s
134
+ self.inspect
135
+ end
136
+
137
+ ##
138
+ # call-seq:
139
+ # Message.[](index) => value
140
+ # Accesses a field's value by field name. The provided field name
141
+ # should be a string.
142
+ def [](name)
143
+ raise TypeError.new "Expected String for name but got #{name.class}" unless name.is_a? String
144
+ index_internal name
145
+ end
146
+
147
+ ##
148
+ # call-seq:
149
+ # Message.[]=(index, value)
150
+ # Sets a field's value by field name. The provided field name should
151
+ # be a string.
152
+ # @param name [String] Name of the field to be set
153
+ # @param value [Object] Value to set the field to
154
+ def []=(name, value)
155
+ raise TypeError.new "Expected String for name but got #{name.class}" unless name.is_a? String
156
+ index_assign_internal(value, name: name)
157
+ end
158
+
159
+ ##
160
+ # call-seq:
161
+ # MessageClass.decode(data, options) => message
162
+ #
163
+ # Decodes the given data (as a string containing bytes in protocol buffers wire
164
+ # format) under the interpretation given by this message class's definition
165
+ # and returns a message object with the corresponding field values.
166
+ # @param data [String] Binary string in Protobuf wire format to decode
167
+ # @param options [Hash] options for the decoder
168
+ # @option options [Integer] :recursion_limit Set to maximum decoding depth for message (default is 64)
169
+ def self.decode(data, options = {})
170
+ raise ArgumentError.new "Expected hash arguments." unless options.is_a? Hash
171
+ raise ArgumentError.new "Expected string for binary protobuf data." unless data.is_a? String
172
+ decoding_options = 0
173
+ depth = options[:recursion_limit]
174
+
175
+ if depth.is_a? Numeric
176
+ decoding_options |= Google::Protobuf::FFI.decode_max_depth(depth.to_i)
177
+ end
178
+
179
+ message = new
180
+ mini_table_ptr = Google::Protobuf::FFI.get_mini_table(message.class.descriptor)
181
+ status = Google::Protobuf::FFI.decode_message(
182
+ data,
183
+ data.bytesize,
184
+ message.instance_variable_get(:@msg),
185
+ mini_table_ptr,
186
+ Google::Protobuf::FFI.get_extension_registry(message.class.descriptor.send(:pool).descriptor_pool),
187
+ decoding_options,
188
+ message.instance_variable_get(:@arena)
189
+ )
190
+ raise ParseError.new "Error occurred during parsing" unless status == :Ok
191
+ message
192
+ end
193
+
194
+ ##
195
+ # call-seq:
196
+ # MessageClass.encode(msg, options) => bytes
197
+ #
198
+ # Encodes the given message object to its serialized form in protocol buffers
199
+ # wire format.
200
+ # @param options [Hash] options for the encoder
201
+ # @option options [Integer] :recursion_limit Set to maximum encoding depth for message (default is 64)
202
+ def self.encode(message, options = {})
203
+ raise ArgumentError.new "Message of wrong type." unless message.is_a? self
204
+ raise ArgumentError.new "Expected hash arguments." unless options.is_a? Hash
205
+
206
+ encoding_options = 0
207
+ depth = options[:recursion_limit]
208
+
209
+ if depth.is_a? Numeric
210
+ encoding_options |= Google::Protobuf::FFI.decode_max_depth(depth.to_i)
211
+ end
212
+
213
+ encode_internal(message.instance_variable_get(:@msg), encoding_options) do |encoding, size, _|
214
+ if encoding.nil? or encoding.null?
215
+ raise RuntimeError.new "Exceeded maximum depth (possibly cycle)"
216
+ else
217
+ encoding.read_string_length(size).force_encoding("ASCII-8BIT").freeze
218
+ end
219
+ end
220
+ end
221
+
222
+ ##
223
+ # all-seq:
224
+ # MessageClass.decode_json(data, options = {}) => message
225
+ #
226
+ # Decodes the given data (as a string containing bytes in protocol buffers wire
227
+ # format) under the interpretation given by this message class's definition
228
+ # and returns a message object with the corresponding field values.
229
+ #
230
+ # @param options [Hash] options for the decoder
231
+ # @option options [Boolean] :ignore_unknown_fields Set true to ignore unknown fields (default is to raise an error)
232
+ # @return [Message]
233
+ def self.decode_json(data, options = {})
234
+ decoding_options = 0
235
+ unless options.is_a? Hash
236
+ if options.respond_to? :to_h
237
+ options options.to_h
238
+ else
239
+ #TODO can this error message be improve to include what was received?
240
+ raise ArgumentError.new "Expected hash arguments"
241
+ end
242
+ end
243
+ raise ArgumentError.new "Expected string for JSON data." unless data.is_a? String
244
+ raise RuntimeError.new "Cannot parse a wrapper directly" if descriptor.send(:wrapper?)
245
+
246
+ if options[:ignore_unknown_fields]
247
+ decoding_options |= Google::Protobuf::FFI::Upb_JsonDecode_IgnoreUnknown
248
+ end
249
+
250
+ message = new
251
+ pool_def = message.class.descriptor.instance_variable_get(:@descriptor_pool).descriptor_pool
252
+ status = Google::Protobuf::FFI::Status.new
253
+ unless Google::Protobuf::FFI.json_decode_message(data, data.bytesize, message.instance_variable_get(:@msg), message.class.descriptor, pool_def, decoding_options, message.instance_variable_get(:@arena), status)
254
+ raise ParseError.new "Error occurred during parsing: #{Google::Protobuf::FFI.error_message(status)}"
255
+ end
256
+ message
257
+ end
258
+
259
+ def self.encode_json(message, options = {})
260
+ encoding_options = 0
261
+ unless options.is_a? Hash
262
+ if options.respond_to? :to_h
263
+ options = options.to_h
264
+ else
265
+ #TODO can this error message be improve to include what was received?
266
+ raise ArgumentError.new "Expected hash arguments"
267
+ end
268
+ end
269
+
270
+ if options[:preserve_proto_fieldnames]
271
+ encoding_options |= Google::Protobuf::FFI::Upb_JsonEncode_UseProtoNames
272
+ end
273
+ if options[:emit_defaults]
274
+ encoding_options |= Google::Protobuf::FFI::Upb_JsonEncode_EmitDefaults
275
+ end
276
+ if options[:format_enums_as_integers]
277
+ encoding_options |= Google::Protobuf::FFI::Upb_JsonEncode_FormatEnumsAsIntegers
278
+ end
279
+
280
+ buffer_size = 1024
281
+ buffer = ::FFI::MemoryPointer.new(:char, buffer_size)
282
+ status = Google::Protobuf::FFI::Status.new
283
+ msg = message.instance_variable_get(:@msg)
284
+ pool_def = message.class.descriptor.instance_variable_get(:@descriptor_pool).descriptor_pool
285
+ size = Google::Protobuf::FFI::json_encode_message(msg, message.class.descriptor, pool_def, encoding_options, buffer, buffer_size, status)
286
+ unless status[:ok]
287
+ raise ParseError.new "Error occurred during encoding: #{Google::Protobuf::FFI.error_message(status)}"
288
+ end
289
+
290
+ if size >= buffer_size
291
+ buffer_size = size + 1
292
+ buffer = ::FFI::MemoryPointer.new(:char, buffer_size)
293
+ status.clear
294
+ size = Google::Protobuf::FFI::json_encode_message(msg, message.class.descriptor, pool_def, encoding_options, buffer, buffer_size, status)
295
+ unless status[:ok]
296
+ raise ParseError.new "Error occurred during encoding: #{Google::Protobuf::FFI.error_message(status)}"
297
+ end
298
+ if size >= buffer_size
299
+ raise ParseError.new "Inconsistent JSON encoding sizes - was #{buffer_size - 1}, now #{size}"
300
+ end
301
+ end
302
+
303
+ buffer.read_string_length(size).force_encoding("UTF-8").freeze
304
+ end
305
+
306
+ private
307
+ # Implementation details below are subject to breaking changes without
308
+ # warning and are intended for use only within the gem.
309
+
310
+ include Google::Protobuf::Internal::Convert
311
+
312
+ def self.setup_accessors!
313
+ @descriptor.each do |field_descriptor|
314
+ field_name = field_descriptor.name
315
+ unless instance_methods(true).include?(field_name.to_sym)
316
+ #TODO - at a high level, dispatching to either
317
+ # index_internal or get_field would be logically correct, but slightly slower.
318
+ if field_descriptor.map?
319
+ define_method(field_name) do
320
+ mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena
321
+ get_map_field(mutable_message_value[:map], field_descriptor)
322
+ end
323
+ elsif field_descriptor.repeated?
324
+ define_method(field_name) do
325
+ mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena
326
+ get_repeated_field(mutable_message_value[:array], field_descriptor)
327
+ end
328
+ elsif field_descriptor.sub_message?
329
+ define_method(field_name) do
330
+ return nil unless Google::Protobuf::FFI.get_message_has @msg, field_descriptor
331
+ mutable_message = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena
332
+ sub_message = mutable_message[:msg]
333
+ sub_message_def = Google::Protobuf::FFI.get_subtype_as_message(field_descriptor)
334
+ Descriptor.send(:get_message, sub_message, sub_message_def, @arena)
335
+ end
336
+ else
337
+ c_type = field_descriptor.send(:c_type)
338
+ if c_type == :enum
339
+ define_method(field_name) do
340
+ message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
341
+ convert_upb_to_ruby message_value, c_type, Google::Protobuf::FFI.get_subtype_as_enum(field_descriptor)
342
+ end
343
+ else
344
+ define_method(field_name) do
345
+ message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
346
+ convert_upb_to_ruby message_value, c_type
347
+ end
348
+ end
349
+ end
350
+ define_method("#{field_name}=") do |value|
351
+ index_assign_internal(value, field_descriptor: field_descriptor)
352
+ end
353
+ define_method("clear_#{field_name}") do
354
+ clear_internal(field_descriptor)
355
+ end
356
+ if field_descriptor.type == :enum
357
+ define_method("#{field_name}_const") do
358
+ if field_descriptor.repeated?
359
+ return_value = []
360
+ get_field(field_descriptor).send(:each_msg_val) do |msg_val|
361
+ return_value << msg_val[:int32_val]
362
+ end
363
+ return_value
364
+ else
365
+ message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor
366
+ message_value[:int32_val]
367
+ end
368
+ end
369
+ end
370
+ if !field_descriptor.repeated? and field_descriptor.wrapper?
371
+ define_method("#{field_name}_as_value") do
372
+ get_field(field_descriptor, unwrap: true)
373
+ end
374
+ define_method("#{field_name}_as_value=") do |value|
375
+ if value.nil?
376
+ clear_internal(field_descriptor)
377
+ else
378
+ index_assign_internal(value, field_descriptor: field_descriptor, wrap: true)
379
+ end
380
+ end
381
+ end
382
+ if field_descriptor.has_presence?
383
+ define_method("has_#{field_name}?") do
384
+ Google::Protobuf::FFI.get_message_has(@msg, field_descriptor)
385
+ end
386
+ end
387
+ end
388
+ end
389
+ end
390
+
391
+ def self.setup_oneof_accessors!
392
+ @oneof_field_names = []
393
+ @descriptor.each_oneof do |oneof_descriptor|
394
+ self.add_oneof_accessors_for! oneof_descriptor
395
+ end
396
+ end
397
+ def self.add_oneof_accessors_for!(oneof_descriptor)
398
+ field_name = oneof_descriptor.name.to_sym
399
+ @oneof_field_names << field_name
400
+ unless instance_methods(true).include?(field_name)
401
+ define_method(field_name) do
402
+ field_descriptor = Google::Protobuf::FFI.get_message_which_oneof(@msg, oneof_descriptor)
403
+ if field_descriptor.nil?
404
+ return
405
+ else
406
+ return field_descriptor.name.to_sym
407
+ end
408
+ end
409
+ define_method("clear_#{field_name}") do
410
+ field_descriptor = Google::Protobuf::FFI.get_message_which_oneof(@msg, oneof_descriptor)
411
+ unless field_descriptor.nil?
412
+ clear_internal(field_descriptor)
413
+ end
414
+ end
415
+ define_method("has_#{field_name}?") do
416
+ !Google::Protobuf::FFI.get_message_which_oneof(@msg, oneof_descriptor).nil?
417
+ end
418
+ end
419
+ end
420
+
421
+ setup_accessors!
422
+ setup_oneof_accessors!
423
+
424
+ def self.private_constructor(arena, msg: nil, initial_value: nil)
425
+ instance = allocate
426
+ instance.send(:initialize, initial_value, arena, msg)
427
+ instance
428
+ end
429
+
430
+ def self.inspect_field(field_descriptor, c_type, message_value)
431
+ if field_descriptor.sub_message?
432
+ sub_msg_descriptor = Google::Protobuf::FFI.get_subtype_as_message(field_descriptor)
433
+ sub_msg_descriptor.msgclass.send(:inspect_internal, message_value[:msg_val])
434
+ else
435
+ convert_upb_to_ruby(message_value, c_type, field_descriptor.subtype).inspect
436
+ end
437
+ end
438
+
439
+ # @param msg [::FFI::Pointer] Pointer to the Message
440
+ def self.inspect_internal(msg)
441
+ field_output = []
442
+ descriptor.each do |field_descriptor|
443
+ next if field_descriptor.has_presence? && !Google::Protobuf::FFI.get_message_has(msg, field_descriptor)
444
+ if field_descriptor.map?
445
+ # TODO Adapted - from map#each_msg_val and map#inspect- can this be refactored to reduce echo without introducing a arena allocation?
446
+ message_descriptor = field_descriptor.subtype
447
+ key_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 1)
448
+ key_field_type = Google::Protobuf::FFI.get_type(key_field_def)
449
+
450
+ value_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 2)
451
+ value_field_type = Google::Protobuf::FFI.get_type(value_field_def)
452
+
453
+ message_value = Google::Protobuf::FFI.get_message_value(msg, field_descriptor)
454
+ iter = ::FFI::MemoryPointer.new(:size_t, 1)
455
+ iter.write(:size_t, Google::Protobuf::FFI::Upb_Map_Begin)
456
+ key_value_pairs = []
457
+ while Google::Protobuf::FFI.map_next(message_value[:map_val], iter) do
458
+ iter_size_t = iter.read(:size_t)
459
+ key_message_value = Google::Protobuf::FFI.map_key(message_value[:map_val], iter_size_t)
460
+ value_message_value = Google::Protobuf::FFI.map_value(message_value[:map_val], iter_size_t)
461
+ key_string = convert_upb_to_ruby(key_message_value, key_field_type).inspect
462
+ value_string = inspect_field(value_field_def, value_field_type, value_message_value)
463
+ key_value_pairs << "#{key_string}=>#{value_string}"
464
+ end
465
+ field_output << "#{field_descriptor.name}: {#{key_value_pairs.join(", ")}}"
466
+ elsif field_descriptor.repeated?
467
+ # TODO Adapted - from repeated_field#each - can this be refactored to reduce echo?
468
+ repeated_field_output = []
469
+ message_value = Google::Protobuf::FFI.get_message_value(msg, field_descriptor)
470
+ array = message_value[:array_val]
471
+ n = array.null? ? 0 : Google::Protobuf::FFI.array_size(array)
472
+ 0.upto(n - 1) do |i|
473
+ element = Google::Protobuf::FFI.get_msgval_at(array, i)
474
+ repeated_field_output << inspect_field(field_descriptor, field_descriptor.send(:c_type), element)
475
+ end
476
+ field_output << "#{field_descriptor.name}: [#{repeated_field_output.join(", ")}]"
477
+ else
478
+ message_value = Google::Protobuf::FFI.get_message_value msg, field_descriptor
479
+ rendered_value = inspect_field(field_descriptor, field_descriptor.send(:c_type), message_value)
480
+ field_output << "#{field_descriptor.name}: #{rendered_value}"
481
+ end
482
+ end
483
+ "<#{name}: #{field_output.join(', ')}>"
484
+ end
485
+
486
+ def self.deep_copy(msg, arena = nil)
487
+ arena ||= Google::Protobuf::FFI.create_arena
488
+ encode_internal(msg) do |encoding, size, mini_table_ptr|
489
+ message = private_constructor(arena)
490
+ if encoding.nil? or encoding.null? or Google::Protobuf::FFI.decode_message(encoding, size, message.instance_variable_get(:@msg), mini_table_ptr, nil, 0, arena) != :Ok
491
+ raise ParseError.new "Error occurred copying proto"
492
+ end
493
+ message
494
+ end
495
+ end
496
+
497
+ def self.encode_internal(msg, encoding_options = 0)
498
+ temporary_arena = Google::Protobuf::FFI.create_arena
499
+
500
+ mini_table_ptr = Google::Protobuf::FFI.get_mini_table(descriptor)
501
+ size_ptr = ::FFI::MemoryPointer.new(:size_t, 1)
502
+ pointer_ptr = ::FFI::MemoryPointer.new(:pointer, 1)
503
+ encoding_status = Google::Protobuf::FFI.encode_message(msg, mini_table_ptr, encoding_options, temporary_arena, pointer_ptr.to_ptr, size_ptr)
504
+ raise "Encoding failed due to #{encoding_status}" unless encoding_status == :Ok
505
+ yield pointer_ptr.read(:pointer), size_ptr.read(:size_t), mini_table_ptr
506
+ end
507
+
508
+ def method_missing_internal(method_name, *args, mode: nil)
509
+ raise ArgumentError.new "method_missing_internal called with invalid mode #{mode.inspect}" unless [:respond_to_missing?, :method_missing].include? mode
510
+
511
+ #TODO not being allowed is not the same thing as not responding, but this is needed to pass tests
512
+ if method_name.to_s.end_with? '='
513
+ if self.class.send(:oneof_field_names).include? method_name.to_s[0..-2].to_sym
514
+ return false if mode == :respond_to_missing?
515
+ raise RuntimeError.new "Oneof accessors are read-only."
516
+ end
517
+ end
518
+
519
+ original_method_missing(method_name, *args) if mode == :method_missing
520
+ end
521
+
522
+ def clear_internal(field_def)
523
+ raise FrozenError.new "can't modify frozen #{self.class}" if frozen?
524
+ Google::Protobuf::FFI.clear_message_field(@msg, field_def)
525
+ end
526
+
527
+ def index_internal(name)
528
+ field_descriptor = self.class.descriptor.lookup(name)
529
+ get_field field_descriptor unless field_descriptor.nil?
530
+ end
531
+
532
+ #TODO - well known types keeps us on our toes by overloading methods.
533
+ # How much of the public API needs to be defended?
534
+ def index_assign_internal(value, name: nil, field_descriptor: nil, wrap: false)
535
+ raise FrozenError.new "can't modify frozen #{self.class}" if frozen?
536
+ if field_descriptor.nil?
537
+ field_descriptor = self.class.descriptor.lookup(name)
538
+ if field_descriptor.nil?
539
+ raise ArgumentError.new "Unknown field: #{name}"
540
+ end
541
+ end
542
+ unless field_descriptor.send :set_value_on_message, value, @msg, @arena, wrap: wrap
543
+ raise RuntimeError.new "allocation failed"
544
+ end
545
+ end
546
+
547
+ ##
548
+ # @param initial_value [Object] initial value of this Message
549
+ # @param arena [Arena] Optional; Arena where this message will be allocated
550
+ # @param msg [::FFI::Pointer] Optional; Message to initialize; creates
551
+ # one if omitted or nil.
552
+ def initialize(initial_value = nil, arena = nil, msg = nil)
553
+ @arena = arena || Google::Protobuf::FFI.create_arena
554
+ @msg = msg || Google::Protobuf::FFI.new_message_from_def(self.class.descriptor, @arena)
555
+
556
+ unless initial_value.nil?
557
+ raise ArgumentError.new "Expected hash arguments or message, not #{initial_value.class}" unless initial_value.respond_to? :each
558
+
559
+ field_def_ptr = ::FFI::MemoryPointer.new :pointer
560
+ oneof_def_ptr = ::FFI::MemoryPointer.new :pointer
561
+
562
+ initial_value.each do |key, value|
563
+ raise ArgumentError.new "Expected string or symbols as hash keys when initializing proto from hash." unless [String, Symbol].include? key.class
564
+
565
+ unless Google::Protobuf::FFI.find_msg_def_by_name self.class.descriptor, key.to_s, key.to_s.bytesize, field_def_ptr, oneof_def_ptr
566
+ raise ArgumentError.new "Unknown field name '#{key}' in initialization map entry."
567
+ end
568
+ raise NotImplementedError.new "Haven't added oneofsupport yet" unless oneof_def_ptr.get_pointer(0).null?
569
+ raise NotImplementedError.new "Expected a field def" if field_def_ptr.get_pointer(0).null?
570
+
571
+ field_descriptor = FieldDescriptor.from_native field_def_ptr.get_pointer(0)
572
+
573
+ next if value.nil?
574
+ if field_descriptor.map?
575
+ index_assign_internal(Google::Protobuf::Map.send(:construct_for_field, field_descriptor, @arena, value: value), name: key.to_s)
576
+ elsif field_descriptor.repeated?
577
+ index_assign_internal(RepeatedField.send(:construct_for_field, field_descriptor, @arena, values: value), name: key.to_s)
578
+ else
579
+ index_assign_internal(value, name: key.to_s)
580
+ end
581
+ end
582
+ end
583
+
584
+ # Should always be the last expression of the initializer to avoid
585
+ # leaking references to this object before construction is complete.
586
+ Google::Protobuf::OBJECT_CACHE.try_add @msg.address, self
587
+ end
588
+
589
+ ##
590
+ # Gets a field of this message identified by the argument definition.
591
+ #
592
+ # @param field [FieldDescriptor] Descriptor of the field to get
593
+ def get_field(field, unwrap: false)
594
+ if field.map?
595
+ mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field, @arena
596
+ get_map_field(mutable_message_value[:map], field)
597
+ elsif field.repeated?
598
+ mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field, @arena
599
+ get_repeated_field(mutable_message_value[:array], field)
600
+ elsif field.sub_message?
601
+ return nil unless Google::Protobuf::FFI.get_message_has @msg, field
602
+ sub_message_def = Google::Protobuf::FFI.get_subtype_as_message(field)
603
+ if unwrap
604
+ if field.has?(self)
605
+ wrapper_message_value = Google::Protobuf::FFI.get_message_value @msg, field
606
+ fields = Google::Protobuf::FFI.field_count(sub_message_def)
607
+ raise "Sub message has #{fields} fields! Expected exactly 1." unless fields == 1
608
+ value_field_def = Google::Protobuf::FFI.get_field_by_number sub_message_def, 1
609
+ message_value = Google::Protobuf::FFI.get_message_value wrapper_message_value[:msg_val], value_field_def
610
+ convert_upb_to_ruby message_value, Google::Protobuf::FFI.get_c_type(value_field_def)
611
+ else
612
+ nil
613
+ end
614
+ else
615
+ mutable_message = Google::Protobuf::FFI.get_mutable_message @msg, field, @arena
616
+ sub_message = mutable_message[:msg]
617
+ Descriptor.send(:get_message, sub_message, sub_message_def, @arena)
618
+ end
619
+ else
620
+ c_type = field.send(:c_type)
621
+ message_value = Google::Protobuf::FFI.get_message_value @msg, field
622
+ if c_type == :enum
623
+ convert_upb_to_ruby message_value, c_type, Google::Protobuf::FFI.get_subtype_as_enum(field)
624
+ else
625
+ convert_upb_to_ruby message_value, c_type
626
+ end
627
+ end
628
+ end
629
+
630
+ ##
631
+ # @param array [::FFI::Pointer] Pointer to the Array
632
+ # @param field [Google::Protobuf::FieldDescriptor] Type of the repeated field
633
+ def get_repeated_field(array, field)
634
+ return nil if array.nil? or array.null?
635
+ repeated_field = OBJECT_CACHE.get(array.address)
636
+ if repeated_field.nil?
637
+ repeated_field = RepeatedField.send(:construct_for_field, field, @arena, array: array)
638
+ repeated_field.freeze if frozen?
639
+ end
640
+ repeated_field
641
+ end
642
+
643
+ ##
644
+ # @param map [::FFI::Pointer] Pointer to the Map
645
+ # @param field [Google::Protobuf::FieldDescriptor] Type of the map field
646
+ def get_map_field(map, field)
647
+ return nil if map.nil? or map.null?
648
+ map_field = OBJECT_CACHE.get(map.address)
649
+ if map_field.nil?
650
+ map_field = Google::Protobuf::Map.send(:construct_for_field, field, @arena, map: map)
651
+ map_field.freeze if frozen?
652
+ end
653
+ map_field
654
+ end
655
+ end
656
+ end
657
+ end
658
+ end
659
+ end