google-protobuf 3.22.0 → 3.25.5

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