google-protobuf 3.24.1 → 3.25.2

Sign up to get free protection for your applications and to get access to all the features.
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 -26
  5. data/ext/google/protobuf_c/defs.c +142 -30
  6. data/ext/google/protobuf_c/defs.h +3 -26
  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 +23 -26
  10. data/ext/google/protobuf_c/map.h +6 -26
  11. data/ext/google/protobuf_c/message.c +67 -77
  12. data/ext/google/protobuf_c/message.h +10 -26
  13. data/ext/google/protobuf_c/protobuf.c +3 -26
  14. data/ext/google/protobuf_c/protobuf.h +3 -26
  15. data/ext/google/protobuf_c/repeated_field.c +23 -27
  16. data/ext/google/protobuf_c/repeated_field.h +6 -26
  17. data/ext/google/protobuf_c/ruby-upb.c +8351 -8160
  18. data/ext/google/protobuf_c/ruby-upb.h +4077 -3784
  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 +1 -1
  25. data/lib/google/protobuf/api_pb.rb +1 -1
  26. data/lib/google/protobuf/descriptor_pb.rb +6 -3
  27. data/lib/google/protobuf/duration_pb.rb +1 -1
  28. data/lib/google/protobuf/empty_pb.rb +1 -1
  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 +1 -1
  45. data/lib/google/protobuf/message_exts.rb +3 -26
  46. data/lib/google/protobuf/object_cache.rb +3 -26
  47. data/lib/google/protobuf/plugin_pb.rb +1 -1
  48. data/lib/google/protobuf/repeated_field.rb +3 -26
  49. data/lib/google/protobuf/source_context_pb.rb +1 -1
  50. data/lib/google/protobuf/struct_pb.rb +1 -1
  51. data/lib/google/protobuf/timestamp_pb.rb +1 -1
  52. data/lib/google/protobuf/type_pb.rb +1 -1
  53. data/lib/google/protobuf/well_known_types.rb +3 -26
  54. data/lib/google/protobuf/wrappers_pb.rb +1 -1
  55. data/lib/google/protobuf.rb +26 -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 +70 -3
@@ -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