grpc-dc 0.37.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.
@@ -0,0 +1,677 @@
1
+ # @generated by grpc-dc codegen. DO NOT EDIT.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ # Dynamic, descriptor-driven client and server.
4
+ #
5
+ # Parses a FileDescriptorSet (as produced by
6
+ # `protoc --descriptor_set_out`) at runtime and builds clients
7
+ # and servers whose messages are generic field maps — no
8
+ # generated stubs required. This is the runtime-generation
9
+ # counterpart of the protoc-plugin surfaces: schemas arrive as
10
+ # data (descriptor.proto), not as code.
11
+ #
12
+ # Generic values by field type: varint family -> Integer,
13
+ # string -> String, bytes/message -> String / GenericMessage,
14
+ # fixed/float -> Integer / Float, bool -> true/false. Repeated
15
+ # fields are arrays in field-number order.
16
+
17
+ require_relative "codec"
18
+ require_relative "server"
19
+ require_relative "wire_gen"
20
+
21
+ module Grpcdc
22
+ # FieldDescriptorProto.Type values (descriptor.proto).
23
+ TYPE_DOUBLE = 1
24
+ TYPE_FLOAT = 2
25
+ TYPE_INT64 = 3
26
+ TYPE_UINT64 = 4
27
+ TYPE_INT32 = 5
28
+ TYPE_FIXED64 = 6
29
+ TYPE_FIXED32 = 7
30
+ TYPE_BOOL = 8
31
+ TYPE_STRING = 9
32
+ TYPE_GROUP = 10
33
+ TYPE_MESSAGE = 11
34
+ TYPE_BYTES = 12
35
+ TYPE_UINT32 = 13
36
+ TYPE_ENUM = 14
37
+ TYPE_SFIXED32 = 15
38
+ TYPE_SFIXED64 = 16
39
+ TYPE_SINT32 = 17
40
+ TYPE_SINT64 = 18
41
+
42
+ VARINT_TYPES = [TYPE_INT64, TYPE_UINT64, TYPE_INT32, TYPE_BOOL,
43
+ TYPE_UINT32, TYPE_ENUM, TYPE_SINT32, TYPE_SINT64].freeze
44
+ LEN_TYPES = [TYPE_STRING, TYPE_BYTES, TYPE_MESSAGE, TYPE_GROUP].freeze
45
+
46
+ WIRE_TYPE = {}.tap do |table|
47
+ VARINT_TYPES.each { |t| table[t] = 0 }
48
+ [TYPE_FIXED64, TYPE_SFIXED64, TYPE_DOUBLE].each { |t| table[t] = 1 }
49
+ LEN_TYPES.each { |t| table[t] = 2 }
50
+ [TYPE_FIXED32, TYPE_SFIXED32, TYPE_FLOAT].each { |t| table[t] = 5 }
51
+ end.freeze
52
+
53
+ LABEL_REPEATED = 3
54
+
55
+ # Minimal protobuf reader over a binary String.
56
+ class PbReader
57
+ attr_reader :last_chunk
58
+
59
+ def initialize(data)
60
+ @buf = data.b
61
+ @pos = 0
62
+ @last_chunk = nil
63
+ end
64
+
65
+ def varint
66
+ result = 0
67
+ shift = 0
68
+ loop do
69
+ raise ArgumentError, "truncated varint" if @pos >= @buf.bytesize
70
+
71
+ b = @buf.getbyte(@pos)
72
+ @pos += 1
73
+ result |= (b & 0x7F) << shift
74
+ return result if (b & 0x80).zero?
75
+
76
+ shift += 7
77
+ raise ArgumentError, "varint too long" if shift >= 70
78
+ end
79
+ end
80
+
81
+ def bytes_field
82
+ n = varint
83
+ raise ArgumentError, "truncated bytes field" if @pos + n > @buf.bytesize
84
+
85
+ out = @buf.byteslice(@pos, n)
86
+ @pos += n
87
+ out
88
+ end
89
+
90
+ # Yield [number, wire_type, value] until end of buffer.
91
+ # Sets last_chunk to the raw bytes of the yielded field
92
+ # (captured before the yield, so consumers may read it while
93
+ # processing this field).
94
+ def each_field
95
+ return enum_for(:each_field) unless block_given?
96
+
97
+ while @pos < @buf.bytesize
98
+ start = @pos
99
+ tag = varint
100
+ number = tag >> 3
101
+ wire = tag & 7
102
+ value =
103
+ case wire
104
+ when 0 then varint
105
+ when 2 then bytes_field
106
+ when 1
107
+ raise ArgumentError, "truncated fixed64" if @pos + 8 > @buf.bytesize
108
+
109
+ v = @buf.byteslice(@pos, 8)
110
+ @pos += 8
111
+ v
112
+ when 5
113
+ raise ArgumentError, "truncated fixed32" if @pos + 4 > @buf.bytesize
114
+
115
+ v = @buf.byteslice(@pos, 4)
116
+ @pos += 4
117
+ v
118
+ else
119
+ raise ArgumentError, "unsupported wire type #{wire}"
120
+ end
121
+ @last_chunk = @buf.byteslice(start...@pos)
122
+ yield number, wire, value
123
+ end
124
+ end
125
+ end
126
+
127
+ class FieldModel
128
+ attr_reader :number, :name, :type, :type_name
129
+
130
+ def initialize(number, name, field_type, label, type_name)
131
+ @number = number
132
+ @name = name
133
+ @type = field_type
134
+ @repeated = label == LABEL_REPEATED
135
+ @type_name = type_name # for TYPE_MESSAGE, e.g. ".echo.EchoRequest"
136
+ end
137
+
138
+ def repeated?
139
+ @repeated
140
+ end
141
+ end
142
+
143
+ class MessageModel
144
+ attr_reader :full_name, :fields, :by_name
145
+
146
+ def initialize(full_name, fields)
147
+ @full_name = full_name
148
+ @fields = fields # number -> FieldModel
149
+ @by_name = {}
150
+ fields.each_value { |f| @by_name[f.name] = f }
151
+ end
152
+
153
+ def refresh_by_name
154
+ @by_name = {}
155
+ @fields.each_value { |f| @by_name[f.name] = f }
156
+ end
157
+ end
158
+
159
+ class MethodModel
160
+ attr_reader :name, :path, :kind, :input, :output
161
+
162
+ def initialize(name, path, kind, input_model, output_model)
163
+ @name = name
164
+ @path = path
165
+ @kind = kind
166
+ @input = input_model
167
+ @output = output_model
168
+ end
169
+ end
170
+
171
+ class ServiceModel
172
+ attr_reader :full_name, :methods
173
+
174
+ def initialize(full_name, methods)
175
+ @full_name = full_name
176
+ @methods = methods # name -> MethodModel
177
+ end
178
+ end
179
+
180
+ # Parsed descriptor set: messages and services by full name.
181
+ class FileModel
182
+ attr_reader :messages, :services
183
+
184
+ def initialize
185
+ @messages = {} # ".echo.EchoRequest" -> MessageModel
186
+ @services = {} # "echo.Echo" -> ServiceModel
187
+ end
188
+
189
+ def message(full_name)
190
+ @messages.fetch(full_name)
191
+ end
192
+
193
+ def service(full_name)
194
+ @services.fetch(full_name)
195
+ end
196
+ end
197
+
198
+ # A message as field number -> list of values (descriptor order).
199
+ #
200
+ # Fields not in the model are retained as raw encoded chunks in
201
+ # `unknown` (protobuf unknown-field retention): a decode ->
202
+ # encode round trip re-emits them after the known fields, so
203
+ # intermediate hops do not strip newer fields.
204
+ class GenericMessage
205
+ attr_accessor :model
206
+ attr_reader :values, :unknown
207
+
208
+ def initialize(model = nil)
209
+ @model = model
210
+ @values = {}
211
+ @unknown = []
212
+ end
213
+
214
+ def set(number, value)
215
+ @values[number] = [value]
216
+ self
217
+ end
218
+
219
+ def add(number, value)
220
+ (@values[number] ||= []) << value
221
+ self
222
+ end
223
+
224
+ def get_all(number)
225
+ (@values[number] || []).dup
226
+ end
227
+
228
+ def get(number, default = nil)
229
+ items = @values[number]
230
+ items && !items.empty? ? items[0] : default
231
+ end
232
+
233
+ def get_string(number, default = "")
234
+ value = get(number)
235
+ value.is_a?(String) ? value : default
236
+ end
237
+
238
+ def get_int(number, default = 0)
239
+ value = get(number)
240
+ value.is_a?(Integer) ? value : default
241
+ end
242
+
243
+ def ==(other)
244
+ other.is_a?(GenericMessage) && @values == other.values
245
+ end
246
+
247
+ def inspect
248
+ "GenericMessage(#{@values.inspect})"
249
+ end
250
+ end
251
+
252
+ class << self
253
+ def encode_varint(value)
254
+ out = String.new(encoding: Encoding::BINARY)
255
+ v = value & 0xFFFFFFFFFFFFFFFF
256
+ while v > 0x7F
257
+ out << ((v & 0x7F) | 0x80)
258
+ v >>= 7
259
+ end
260
+ out << v
261
+ out
262
+ end
263
+
264
+ def encode_generic(model, message, file_model)
265
+ out = String.new(encoding: Encoding::BINARY)
266
+ message.values.keys.sort.each do |number|
267
+ field = model.fields[number]
268
+ if field.nil?
269
+ raise StatusError.new(CODE_INVALID_ARGUMENT, "unknown field #{number} for #{model.full_name}")
270
+ end
271
+
272
+ values = message.values[number]
273
+ if field.repeated?
274
+ values.each { |value| out << encode_value(field, value, file_model) }
275
+ elsif !values.empty?
276
+ out << encode_value(field, values[0], file_model)
277
+ end
278
+ end
279
+ (message.unknown || []).each { |chunk| out << chunk }
280
+ out
281
+ end
282
+
283
+ def decode_generic(model, data, file_model)
284
+ message = GenericMessage.new(model)
285
+ reader = PbReader.new(data)
286
+ reader.each_field do |number, wire, raw|
287
+ field = model.fields[number]
288
+ if field.nil?
289
+ # Unknown field: retain the raw encoded chunk verbatim.
290
+ message.unknown << reader.last_chunk
291
+ next
292
+ end
293
+ (message.values[number] ||= []) << decode_value(field, wire, raw, file_model)
294
+ end
295
+ message
296
+ end
297
+
298
+ # Parse FileDescriptorSet bytes into a FileModel.
299
+ def parse_descriptor_set(data)
300
+ file_model = FileModel.new
301
+ pending_services = []
302
+ PbReader.new(data).each_field do |number, _wire, file_bytes|
303
+ next unless number == 1
304
+
305
+ package = ""
306
+ service_types = []
307
+ fields_of_file = PbReader.new(file_bytes).each_field.to_a
308
+ fields_of_file.each do |fnum, _w, fval|
309
+ package = fval.dup.force_encoding(Encoding::UTF_8) if fnum == 2
310
+ end
311
+ fields_of_file.each do |fnum, _w, fval|
312
+ case fnum
313
+ when 4 then parse_message(package, fval, file_model)
314
+ when 6 then service_types << [package, fval]
315
+ end
316
+ end
317
+ pending_services.concat(service_types)
318
+ end
319
+ pending_services.each { |package, bytes| parse_service(package, bytes, file_model) }
320
+ file_model
321
+ end
322
+
323
+ # Register a runtime-built service on a Grpcdc::Server.
324
+ #
325
+ # handler is an object with methods by kind:
326
+ # unary(ctx, method, request) -> GenericMessage
327
+ # server_stream(ctx, method, request, send) -> nil
328
+ # client_stream(ctx, method, request_enum) -> GenericMessage
329
+ # bidi(ctx, method, request_enum, send) -> nil
330
+ # where send is a callable taking a GenericMessage and
331
+ # request_enum yields GenericMessages until half-close.
332
+ def register_dynamic_service(server, file_model, service_name, handler)
333
+ service = file_model.services.fetch(service_name)
334
+ methods = service.methods.values.map do |method|
335
+ Method.new(method.name, method.path, method.kind, make_handler(handler, method, file_model))
336
+ end
337
+ server.register_service(ServiceDesc.new(service.full_name, methods))
338
+ end
339
+
340
+ private
341
+
342
+ def to_signed(value, bits)
343
+ value &= (1 << bits) - 1
344
+ value >= (1 << (bits - 1)) ? value - (1 << bits) : value
345
+ end
346
+
347
+ def zigzag_decode(value)
348
+ (value >> 1) ^ -(value & 1)
349
+ end
350
+
351
+ def zigzag_encode(value)
352
+ ((value << 1) ^ (value >> 63)) & 0xFFFFFFFFFFFFFFFF
353
+ end
354
+
355
+ def encode_value(field, value, file_model)
356
+ wire = WIRE_TYPE.fetch(field.type)
357
+ out = encode_varint((field.number << 3) | wire)
358
+ case wire
359
+ when 0
360
+ if [TYPE_SINT32, TYPE_SINT64].include?(field.type)
361
+ bits = field.type == TYPE_SINT32 ? 32 : 64
362
+ out << encode_varint(zigzag_encode(value) & ((1 << bits) - 1))
363
+ elsif field.type == TYPE_BOOL
364
+ out << encode_varint(value ? 1 : 0)
365
+ else
366
+ out << encode_varint(value & 0xFFFFFFFFFFFFFFFF)
367
+ end
368
+ when 1
369
+ out << if field.type == TYPE_DOUBLE
370
+ [value].pack("E")
371
+ elsif field.type == TYPE_SFIXED64
372
+ [value].pack("q<")
373
+ else
374
+ [value & 0xFFFFFFFFFFFFFFFF].pack("Q<")
375
+ end
376
+ when 5
377
+ out << if field.type == TYPE_FLOAT
378
+ [value].pack("e")
379
+ elsif field.type == TYPE_SFIXED32
380
+ [value].pack("l<")
381
+ else
382
+ [value & 0xFFFFFFFF].pack("V")
383
+ end
384
+ else
385
+ data =
386
+ case field.type
387
+ when TYPE_STRING then value.encode(Encoding::UTF_8).b
388
+ when TYPE_MESSAGE then encode_generic(file_model.messages.fetch(field.type_name), value, file_model)
389
+ else value.b
390
+ end
391
+ out << encode_varint(data.bytesize)
392
+ out << data
393
+ end
394
+ out
395
+ end
396
+
397
+ def decode_value(field, wire, raw, file_model)
398
+ case wire
399
+ when 0
400
+ case field.type
401
+ when TYPE_BOOL then raw != 0
402
+ when TYPE_SINT32, TYPE_SINT64 then zigzag_decode(raw)
403
+ when TYPE_INT32, TYPE_ENUM then to_signed(raw, 32)
404
+ when TYPE_INT64 then to_signed(raw, 64)
405
+ else raw
406
+ end
407
+ when 1
408
+ case field.type
409
+ when TYPE_DOUBLE then raw.unpack1("E")
410
+ when TYPE_SFIXED64 then raw.unpack1("q<")
411
+ else raw.unpack1("Q<")
412
+ end
413
+ when 5
414
+ case field.type
415
+ when TYPE_FLOAT then raw.unpack1("e")
416
+ when TYPE_SFIXED32 then raw.unpack1("l<")
417
+ else raw.unpack1("V")
418
+ end
419
+ else
420
+ # length-delimited
421
+ case field.type
422
+ when TYPE_STRING then raw.dup.force_encoding(Encoding::UTF_8)
423
+ when TYPE_MESSAGE then decode_generic(file_model.messages.fetch(field.type_name), raw, file_model)
424
+ else raw.b
425
+ end
426
+ end
427
+ end
428
+
429
+ def parse_message(package, data, file_model)
430
+ name = ""
431
+ raw_fields = []
432
+ nested = []
433
+ PbReader.new(data).each_field do |fnum, _w, fval|
434
+ case fnum
435
+ when 1 then name = fval.dup.force_encoding(Encoding::UTF_8)
436
+ when 2 then raw_fields << fval
437
+ when 3 then nested << fval
438
+ end
439
+ end
440
+ full_name = package.empty? ? name : "#{package}.#{name}"
441
+ fields = {}
442
+ model = MessageModel.new(full_name, fields)
443
+ file_model.messages["." + full_name] = model
444
+ nested.each { |bytes| parse_nested(package, name, bytes, file_model) }
445
+ raw_fields.each do |field_bytes|
446
+ field = parse_field(field_bytes)
447
+ fields[field.number] = field
448
+ end
449
+ model.refresh_by_name
450
+ model
451
+ end
452
+
453
+ def parse_nested(package, outer, data, file_model)
454
+ name = ""
455
+ raw_fields = []
456
+ PbReader.new(data).each_field do |fnum, _w, fval|
457
+ case fnum
458
+ when 1 then name = fval.dup.force_encoding(Encoding::UTF_8)
459
+ when 2 then raw_fields << fval
460
+ end
461
+ end
462
+ full_name = package.empty? ? "#{outer}.#{name}" : "#{package}.#{outer}.#{name}"
463
+ fields = {}
464
+ model = MessageModel.new(full_name, fields)
465
+ file_model.messages["." + full_name] = model
466
+ raw_fields.each do |field_bytes|
467
+ field = parse_field(field_bytes)
468
+ fields[field.number] = field
469
+ end
470
+ model.refresh_by_name
471
+ model
472
+ end
473
+
474
+ def parse_field(data)
475
+ name = ""
476
+ number = 0
477
+ label = 1
478
+ field_type = 0
479
+ type_name = ""
480
+ PbReader.new(data).each_field do |fnum, _w, fval|
481
+ case fnum
482
+ when 1 then name = fval.dup.force_encoding(Encoding::UTF_8)
483
+ when 3 then number = fval
484
+ when 4 then label = fval
485
+ when 5 then field_type = fval
486
+ when 6 then type_name = fval.dup.force_encoding(Encoding::UTF_8)
487
+ end
488
+ end
489
+ FieldModel.new(number, name, field_type, label, type_name)
490
+ end
491
+
492
+ def kind_of(client_streaming, server_streaming)
493
+ if client_streaming && server_streaming
494
+ KIND_BIDI
495
+ elsif server_streaming
496
+ KIND_SERVER_STREAMING
497
+ elsif client_streaming
498
+ KIND_CLIENT_STREAMING
499
+ else
500
+ KIND_UNARY
501
+ end
502
+ end
503
+
504
+ def parse_service(package, data, file_model)
505
+ name = ""
506
+ raw_methods = []
507
+ PbReader.new(data).each_field do |fnum, _w, fval|
508
+ case fnum
509
+ when 1 then name = fval.dup.force_encoding(Encoding::UTF_8)
510
+ when 2 then raw_methods << fval
511
+ end
512
+ end
513
+ full_name = package.empty? ? name : "#{package}.#{name}"
514
+ methods = {}
515
+ raw_methods.each do |method_bytes|
516
+ mname = ""
517
+ input_type = ""
518
+ output_type = ""
519
+ client_streaming = false
520
+ server_streaming = false
521
+ PbReader.new(method_bytes).each_field do |fnum, _w, fval|
522
+ case fnum
523
+ when 1 then mname = fval.dup.force_encoding(Encoding::UTF_8)
524
+ when 2 then input_type = fval.dup.force_encoding(Encoding::UTF_8)
525
+ when 3 then output_type = fval.dup.force_encoding(Encoding::UTF_8)
526
+ when 5 then client_streaming = fval != 0
527
+ when 6 then server_streaming = fval != 0
528
+ end
529
+ end
530
+ methods[mname] = MethodModel.new(
531
+ mname, "/#{full_name}/#{mname}", kind_of(client_streaming, server_streaming),
532
+ file_model.messages.fetch(input_type), file_model.messages.fetch(output_type)
533
+ )
534
+ end
535
+ service = ServiceModel.new(full_name, methods)
536
+ file_model.services[full_name] = service
537
+ service
538
+ end
539
+
540
+ def make_handler(handler, method, file_model)
541
+ lambda do |ctx, reqs, sink|
542
+ send = lambda do |generic|
543
+ sink.send(encode_generic(method.output, generic, file_model))
544
+ end
545
+ request_enum = Enumerator.new do |yielder|
546
+ loop do
547
+ begin
548
+ payload = reqs.recv_raw
549
+ rescue EOFError
550
+ break
551
+ end
552
+ yielder << decode_generic(method.input, payload, file_model)
553
+ end
554
+ end
555
+ case method.kind
556
+ when KIND_UNARY
557
+ send.call(handler.unary(ctx, method, request_enum.next))
558
+ when KIND_SERVER_STREAMING
559
+ handler.server_stream(ctx, method, request_enum.next, send)
560
+ when KIND_CLIENT_STREAMING
561
+ send.call(handler.client_stream(ctx, method, request_enum))
562
+ else
563
+ handler.bidi(ctx, method, request_enum, send)
564
+ end
565
+ end
566
+ end
567
+ end
568
+
569
+ # Adapts a (MessageModel, GenericMessage) pair to the Channel
570
+ # marshal/unmarshal contract.
571
+ class BoundMessage
572
+ attr_reader :model, :file_model
573
+ attr_accessor :generic
574
+
575
+ def initialize(model, generic, file_model)
576
+ @model = model
577
+ @generic = generic || GenericMessage.new(model)
578
+ @file_model = file_model
579
+ end
580
+
581
+ def marshal_dc
582
+ Grpcdc.encode_generic(@model, @generic, @file_model)
583
+ end
584
+
585
+ def unmarshal_dc(data)
586
+ @generic = Grpcdc.decode_generic(@model, data, @file_model)
587
+ self
588
+ end
589
+ end
590
+
591
+ class DynamicRecvStream
592
+ include Enumerable
593
+
594
+ def initialize(stream, model, file_model)
595
+ @stream = stream
596
+ @model = model
597
+ @file_model = file_model
598
+ end
599
+
600
+ def recv
601
+ @stream.recv(BoundMessage.new(@model, nil, @file_model)).generic
602
+ end
603
+
604
+ # Abandon the call early: RST unless it already terminated.
605
+ def cancel
606
+ @stream.cancel
607
+ end
608
+
609
+ def close
610
+ @stream.cancel
611
+ end
612
+
613
+ def each
614
+ return enum_for(:each) unless block_given?
615
+
616
+ begin
617
+ loop do
618
+ begin
619
+ yield recv
620
+ rescue EOFError
621
+ return
622
+ end
623
+ end
624
+ ensure
625
+ # Breaking out of the block abandons the call: RST so
626
+ # the server stops producing (no-op once terminated).
627
+ close
628
+ end
629
+ end
630
+ end
631
+
632
+ # Runtime-built client: methods resolve from the FileModel.
633
+ class DynamicClient
634
+ def initialize(channel, file_model)
635
+ @channel = channel
636
+ @model = file_model
637
+ end
638
+
639
+ def unary(service_name, method_name, request, metadata = nil, timeout: nil)
640
+ method = resolve(service_name, method_name)
641
+ resp = BoundMessage.new(method.output, nil, @model)
642
+ @channel.unary(method.path, BoundMessage.new(method.input, request, @model), resp, metadata, timeout: timeout)
643
+ resp.generic
644
+ end
645
+
646
+ def server_stream(service_name, method_name, request, metadata = nil, timeout: nil)
647
+ method = resolve(service_name, method_name)
648
+ stream = @channel.server_stream(
649
+ method.path, BoundMessage.new(method.input, request, @model), metadata, timeout: timeout
650
+ )
651
+ DynamicRecvStream.new(stream, method.output, @model)
652
+ end
653
+
654
+ def client_stream(service_name, method_name, requests, metadata = nil, timeout: nil)
655
+ method = resolve(service_name, method_name)
656
+ resp = BoundMessage.new(method.output, nil, @model)
657
+ @channel.client_stream(
658
+ method.path, requests.map { |r| BoundMessage.new(method.input, r, @model) }, resp, metadata, timeout: timeout
659
+ )
660
+ resp.generic
661
+ end
662
+
663
+ def bidi(service_name, method_name, requests, metadata = nil, timeout: nil)
664
+ method = resolve(service_name, method_name)
665
+ stream = @channel.bidi(
666
+ method.path, requests.map { |r| BoundMessage.new(method.input, r, @model) }, metadata, timeout: timeout
667
+ )
668
+ DynamicRecvStream.new(stream, method.output, @model)
669
+ end
670
+
671
+ private
672
+
673
+ def resolve(service_name, method_name)
674
+ @model.services.fetch(service_name).methods.fetch(method_name)
675
+ end
676
+ end
677
+ end