google-protobuf 3.22.2 → 4.30.1

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