fast-protowire 0.2.0 → 0.3.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +58 -0
- data/README.md +3 -3
- data/lib/fast/protowire/field.rb +113 -25
- data/lib/fast/protowire/message.rb +19 -9
- data/lib/fast/protowire/reader.rb +62 -13
- data/lib/fast/protowire/version.rb +1 -1
- data/lib/fast/protowire/wire.rb +12 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9d6f10862b8163b7b4d5210479a6b27b62298e0951b24425db97cdec94356a10
|
|
4
|
+
data.tar.gz: 5a882709766e557ae6a65b3107ec3cc8de6fba07f90de3293607518cf880b4f8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8a54a74df181260136e00d9227f050c1ef785096515b4a91197f939af58df1d865bea3eb5e660db97335111a1728407edccfce11afd8b60a4335076e95b1a258
|
|
7
|
+
data.tar.gz: 5048f2b557f2e3c4f44b4a2cb95110b11142c6b7c94975dbca68f7a3fc1550ea2e9b120460b4e36bb2ed7f316bdb476b60b2b334f449728c2a8ba869242a8036
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
- A declared field arriving with a wire type it does not accept is kept as
|
|
6
|
+
an unknown field and written back verbatim, where it used to raise
|
|
7
|
+
`DecodeError`: schema drift is not corruption, and the reference keeps it.
|
|
8
|
+
So is a map entry carrying more than a key and a value — an undeclared
|
|
9
|
+
subfield, or a key or value with a wire type the entry does not accept —
|
|
10
|
+
kept whole among the parent message's unknown fields and left out of the
|
|
11
|
+
map. An entry that omits its key or its value decodes to that type's
|
|
12
|
+
default, an empty message for a message-typed value.
|
|
13
|
+
- Hostile input is bounded rather than fatal. Nesting deeper than
|
|
14
|
+
`Reader::MAX_DEPTH` (100, the reference's limit; nested messages, map
|
|
15
|
+
entries and groups count, a packed field does not) raises `DecodeError`
|
|
16
|
+
instead of overflowing the VM stack with `SystemStackError`. Field number
|
|
17
|
+
0 anywhere in the input, a group closed by an `END_GROUP` carrying another
|
|
18
|
+
number, and a proto3 `string` whose bytes are not valid UTF-8 raise
|
|
19
|
+
`DecodeError` too. A varint carrying bits above 64 is truncated rather
|
|
20
|
+
than read wide, as every implementation does, and a `sint32` is truncated
|
|
21
|
+
to 32 bits before its zigzag is undone.
|
|
22
|
+
- Decoding is about bytes, so a `Reader` reads its input whatever the String
|
|
23
|
+
is tagged: a non-binary one is read through a binary view made once on
|
|
24
|
+
construction, and the input is never modified. `encode` holds its buffer
|
|
25
|
+
to the same rule: an empty String of any encoding is retagged binary, and
|
|
26
|
+
one already holding text raises `ArgumentError` rather than widening the
|
|
27
|
+
bytes it appends into that encoding's characters.
|
|
28
|
+
- `Message#==`, `eql?` and `hash` compare the declared fields only. Unknown
|
|
29
|
+
fields are no longer part of the comparison, as the reference does not
|
|
30
|
+
compare them; they are still kept, still readable through
|
|
31
|
+
`unknown_fields`, and still written back by `encode`. Byte identity is
|
|
32
|
+
`a.encode == b.encode`.
|
|
33
|
+
- A `float` field narrows to single precision on assignment rather than on
|
|
34
|
+
encode, so what it reads back is what the wire carries and
|
|
35
|
+
`decode(encode(m)) == m` holds: `0.1` reads back `0.10000000149011612`,
|
|
36
|
+
`3.5e38` reads `Infinity`, `1e-50` reads `0.0`. A 32-bit NaN is still
|
|
37
|
+
written canonical (`7fc00000`) because Ruby's `pack("e")` drops the sign
|
|
38
|
+
and payload bits the reference preserves; that one is documented, not
|
|
39
|
+
fixed. `double` NaNs round-trip whole.
|
|
40
|
+
- A declaration the wire format has no form for raises `ArgumentError` where
|
|
41
|
+
it is written rather than at the first encode: a field number outside
|
|
42
|
+
1 ... 2²⁹−1 or inside 19000 ... 19999, which the specification reserves for
|
|
43
|
+
the implementation, and `packed: true` on a `string`, `bytes` or message
|
|
44
|
+
field.
|
|
45
|
+
- `Reader#read_packed` is back for packed fields, which are read in place
|
|
46
|
+
and cost no depth level (0.2.0's entry recorded it removed).
|
|
47
|
+
`Reader#read_key` returns a tag as the raw key holding number and wire
|
|
48
|
+
type together; `Reader#skip` takes the field number the value arrived
|
|
49
|
+
under, `skip(wire_type, number = nil)`, so a group is closed only by its
|
|
50
|
+
own number; `Field#accepts?(wire_type)`, `Wire.binary_buffer` and
|
|
51
|
+
`Wire::UINT64_MASK` are public.
|
|
52
|
+
- Decoding costs what it did on 0.2.0, guards included: the 36,000-metric
|
|
53
|
+
family decodes in 0.801 s where 0.2.0 takes 0.784 s on the same machine
|
|
54
|
+
and Ruby (4.0.7, five timed calls, same session). A field's wire type is
|
|
55
|
+
computed once, when the field is declared, rather than on every read,
|
|
56
|
+
where the per-field check had been paying for it twice. What it allocates
|
|
57
|
+
is unchanged, 1,404,005 objects for that family and one object to encode
|
|
58
|
+
it, and so are the budgets in `test/fast/protowire/allocations.rb`; the
|
|
59
|
+
benchmarks page carries the re-measured tables.
|
|
60
|
+
|
|
3
61
|
## 0.2.0
|
|
4
62
|
|
|
5
63
|
- Encoding allocates nothing per nested message, packed field or map
|
data/README.md
CHANGED
|
@@ -58,11 +58,11 @@ metrics with twelve labels each (Ruby 4.0.7; conditions and every table on the
|
|
|
58
58
|
|
|
59
59
|
- Encoding allocates **one object, the output**, whatever the message's size or depth;
|
|
60
60
|
0.1.0 allocated 647,000 for this family. Decoding allocates only the messages,
|
|
61
|
-
containers and Strings it returns, 5x fewer than 0.1.0 and 1.
|
|
61
|
+
containers and Strings it returns, 5x fewer than 0.1.0 and 1.3x faster.
|
|
62
62
|
- Built a message at a time, the way an exposition builds series, `google-protobuf`
|
|
63
63
|
leaves **504,001 native arenas and 225 MiB** behind for an 11.76 MB body and spends
|
|
64
|
-
1.
|
|
65
|
-
- `google-protobuf` is native, and
|
|
64
|
+
1.77 s of every ten builds in GC; fast-protowire leaves 16.5 MiB, no arenas, and 0.27 s.
|
|
65
|
+
- `google-protobuf` is native, and 9 to 25x faster per operation on an existing tree
|
|
66
66
|
or one nested Hash. This gem trades that speed for memory that is roughly the size of
|
|
67
67
|
the output; fast-prometheus's scrape path goes further and writes series with `Wire`
|
|
68
68
|
directly, with no message per series at all.
|
data/lib/fast/protowire/field.rb
CHANGED
|
@@ -30,9 +30,15 @@ module Fast
|
|
|
30
30
|
|
|
31
31
|
MAP_KEY_TYPES = %i[int32 int64 uint32 uint64 sint32 sint64 fixed32 fixed64 sfixed32 sfixed64 bool string].freeze
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
# The field numbers a tag can carry, less the range the specification
|
|
34
|
+
# reserves for the implementation.
|
|
35
|
+
NUMBERS = (1..(1 << 29) - 1)
|
|
36
|
+
RESERVED_NUMBERS = (19_000..19_999)
|
|
34
37
|
|
|
35
|
-
|
|
38
|
+
private_constant :SCALAR_WIRE_TYPES, :INTEGER_RANGES, :FIXED_FORMATS, :PACKABLE, :MAP_KEY_TYPES,
|
|
39
|
+
:NUMBERS, :RESERVED_NUMBERS
|
|
40
|
+
|
|
41
|
+
attr_reader :name, :number, :type, :rule, :oneof, :ivar, :enum, :wire_type
|
|
36
42
|
|
|
37
43
|
# +type+ is a scalar Symbol, an Enum module, a Message class, or a
|
|
38
44
|
# String / Proc naming a Message class resolved on first use (for
|
|
@@ -40,13 +46,17 @@ module Fast
|
|
|
40
46
|
# :repeated or :map.
|
|
41
47
|
def initialize(name, type, number, rule:, owner:, packed: nil, default: nil, oneof: nil, key_type: nil)
|
|
42
48
|
@name = name
|
|
43
|
-
@number = number
|
|
49
|
+
@number = validate_number(number)
|
|
44
50
|
@rule = rule
|
|
45
51
|
@owner = owner
|
|
46
52
|
@oneof = oneof
|
|
47
53
|
@ivar = :"@#{name}"
|
|
48
54
|
resolve_type(type)
|
|
55
|
+
@wire_type = rule == :map ? Wire::LENGTH_DELIMITED : SCALAR_WIRE_TYPES.fetch(@type)
|
|
56
|
+
raise ArgumentError, "#{name}: a #{@type} field cannot be packed" if packed && !packable?
|
|
57
|
+
|
|
49
58
|
@packed = rule == :repeated && (packed.nil? ? owner.syntax == :proto3 && packable? : packed)
|
|
59
|
+
@strict_utf8 = @type == :string && owner.syntax == :proto3
|
|
50
60
|
@default = default
|
|
51
61
|
@tag = Wire.tag(number, packed? ? Wire::LENGTH_DELIMITED : wire_type)
|
|
52
62
|
return unless map?
|
|
@@ -55,6 +65,9 @@ module Fast
|
|
|
55
65
|
|
|
56
66
|
@key_field = Field.new(:key, key_type, 1, rule: :optional, owner: owner)
|
|
57
67
|
@value_field = Field.new(:value, type, 2, rule: :optional, owner: owner)
|
|
68
|
+
# The only two tags an entry accepts: number and wire type together.
|
|
69
|
+
@key_tag = (1 << 3) | @key_field.wire_type
|
|
70
|
+
@value_tag = (2 << 3) | @value_field.wire_type
|
|
58
71
|
end
|
|
59
72
|
|
|
60
73
|
def message_class
|
|
@@ -65,10 +78,6 @@ module Fast
|
|
|
65
78
|
end
|
|
66
79
|
end
|
|
67
80
|
|
|
68
|
-
def wire_type
|
|
69
|
-
map? ? Wire::LENGTH_DELIMITED : SCALAR_WIRE_TYPES.fetch(type)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
81
|
def repeated?
|
|
73
82
|
rule == :repeated
|
|
74
83
|
end
|
|
@@ -85,6 +94,13 @@ module Fast
|
|
|
85
94
|
PACKABLE.include?(type)
|
|
86
95
|
end
|
|
87
96
|
|
|
97
|
+
# Whether a value of +wire_type+ can be read into this field: its own,
|
|
98
|
+
# plus the packed form of a packable repeated field however it was
|
|
99
|
+
# declared. Anything else is schema drift, kept as an unknown field.
|
|
100
|
+
def accepts?(wire_type)
|
|
101
|
+
wire_type == self.wire_type || (repeated? && packable? && wire_type == Wire::LENGTH_DELIMITED)
|
|
102
|
+
end
|
|
103
|
+
|
|
88
104
|
# Whether an unset field is distinguishable from one set to its default.
|
|
89
105
|
def explicit_presence?
|
|
90
106
|
rule != :implicit || !oneof.nil? || type == :message
|
|
@@ -142,11 +158,13 @@ module Fast
|
|
|
142
158
|
end
|
|
143
159
|
|
|
144
160
|
# Reads one occurrence of this field into +current+ (the value already
|
|
145
|
-
# held) and returns the value to store.
|
|
146
|
-
|
|
161
|
+
# held) and returns the value to store. +message+ is the message being
|
|
162
|
+
# read into, for a map entry it cannot accept: that belongs among the
|
|
163
|
+
# message's unknown fields rather than in the map.
|
|
164
|
+
def decode(reader, wire_type, current, message)
|
|
147
165
|
case rule
|
|
148
166
|
when :repeated then decode_repeated(reader, wire_type, current)
|
|
149
|
-
when :map then decode_map_entry(reader, wire_type, current)
|
|
167
|
+
when :map then decode_map_entry(reader, wire_type, current, message)
|
|
150
168
|
else
|
|
151
169
|
if type == :message && current
|
|
152
170
|
expect(reader, wire_type).read_nested { |nested| current.merge_from(nested) }
|
|
@@ -158,6 +176,15 @@ module Fast
|
|
|
158
176
|
|
|
159
177
|
protected
|
|
160
178
|
|
|
179
|
+
# A second occurrence of a message field merges into the first; of
|
|
180
|
+
# anything else, replaces it. (A map's value field is read this way by
|
|
181
|
+
# the map field, which is why this is protected rather than private.)
|
|
182
|
+
def decode_singular(reader, wire_type, current)
|
|
183
|
+
return read_one(reader, wire_type) unless type == :message && current
|
|
184
|
+
|
|
185
|
+
expect(reader, wire_type).read_nested { |nested| current.merge_from(nested) }
|
|
186
|
+
end
|
|
187
|
+
|
|
161
188
|
def coerce_one(value)
|
|
162
189
|
case type
|
|
163
190
|
when :string then coerce_string(value)
|
|
@@ -174,12 +201,20 @@ module Fast
|
|
|
174
201
|
expect(reader, wire_type)
|
|
175
202
|
case type
|
|
176
203
|
when :message then reader.read_nested { |nested| message_class.new.merge_from(nested) }
|
|
177
|
-
when :string
|
|
204
|
+
when :string
|
|
205
|
+
@strict_utf8 ? read_string(reader) : reader.read_length_delimited.force_encoding(Encoding::UTF_8)
|
|
178
206
|
when :bytes then reader.read_length_delimited
|
|
179
207
|
else read_scalar(reader)
|
|
180
208
|
end
|
|
181
209
|
end
|
|
182
210
|
|
|
211
|
+
# What a map entry holds for this field when the entry omits it: the
|
|
212
|
+
# type's default, except that a message value is an empty instance, as
|
|
213
|
+
# the reference materialises one (and nil would not encode).
|
|
214
|
+
def entry_default
|
|
215
|
+
type == :message ? message_class.new : default_value
|
|
216
|
+
end
|
|
217
|
+
|
|
183
218
|
# Appends the tag and one value. A nested message is encoded straight
|
|
184
219
|
# into the buffer behind a length prefix filled in after it, with no
|
|
185
220
|
# buffer of its own; +width+ carries the prefix width from one value
|
|
@@ -204,6 +239,13 @@ module Fast
|
|
|
204
239
|
|
|
205
240
|
private
|
|
206
241
|
|
|
242
|
+
def validate_number(number)
|
|
243
|
+
raise ArgumentError, "field number #{number} is reserved" if RESERVED_NUMBERS.cover?(number)
|
|
244
|
+
raise ArgumentError, "field number #{number} is outside #{NUMBERS}" unless NUMBERS.cover?(number)
|
|
245
|
+
|
|
246
|
+
number
|
|
247
|
+
end
|
|
248
|
+
|
|
207
249
|
def resolve_type(type)
|
|
208
250
|
case type
|
|
209
251
|
when Symbol
|
|
@@ -292,8 +334,7 @@ module Fast
|
|
|
292
334
|
# Each entry is a message { key = 1; value = 2 }.
|
|
293
335
|
def map_step(ivar)
|
|
294
336
|
tag = @tag
|
|
295
|
-
key =
|
|
296
|
-
value = @value_field.writer
|
|
337
|
+
key, value = entry_writers
|
|
297
338
|
width = 1
|
|
298
339
|
lambda do |message, buffer|
|
|
299
340
|
message.instance_variable_get(ivar).each do |k, v|
|
|
@@ -365,10 +406,13 @@ module Fast
|
|
|
365
406
|
value.encoding == Encoding::BINARY ? value : value.b
|
|
366
407
|
end
|
|
367
408
|
|
|
409
|
+
# A +float+ holds what the wire holds: the value narrowed to single
|
|
410
|
+
# precision on assignment, as the reference narrows it, so what is
|
|
411
|
+
# read back is what a decode of the encoding reads.
|
|
368
412
|
def coerce_float(value)
|
|
369
413
|
raise ::TypeError, "#{name} expects a number, got #{value.class}" unless value.is_a?(Numeric)
|
|
370
414
|
|
|
371
|
-
value.to_f
|
|
415
|
+
type == :float ? [value.to_f].pack("e").unpack1("e") : value.to_f
|
|
372
416
|
end
|
|
373
417
|
|
|
374
418
|
def coerce_bool(value)
|
|
@@ -413,30 +457,64 @@ module Fast
|
|
|
413
457
|
|
|
414
458
|
def decode_repeated(reader, wire_type, values)
|
|
415
459
|
if packable? && wire_type == Wire::LENGTH_DELIMITED
|
|
416
|
-
reader.
|
|
460
|
+
reader.read_packed { |packed| values << read_scalar(packed) until packed.eof? }
|
|
417
461
|
else
|
|
418
462
|
values << read_one(reader, wire_type)
|
|
419
463
|
end
|
|
420
464
|
values
|
|
421
465
|
end
|
|
422
466
|
|
|
423
|
-
|
|
467
|
+
# An entry the map accepts holds its key, its value, or both; one that
|
|
468
|
+
# carries anything else — an undeclared subfield, or the key or value
|
|
469
|
+
# with a wire type the entry does not accept — is not a map entry at
|
|
470
|
+
# all. The reference keeps such an entry among the parent message's
|
|
471
|
+
# unknown fields and leaves the map alone, so this does too.
|
|
472
|
+
def decode_map_entry(reader, wire_type, hash, message)
|
|
424
473
|
expect(reader, wire_type).read_nested do |entry|
|
|
425
|
-
key =
|
|
426
|
-
value =
|
|
474
|
+
key = nil
|
|
475
|
+
value = nil
|
|
476
|
+
unknown = nil
|
|
427
477
|
until entry.eof?
|
|
428
|
-
tag = entry.
|
|
429
|
-
case tag
|
|
430
|
-
when
|
|
431
|
-
when
|
|
432
|
-
else
|
|
478
|
+
tag = entry.read_key
|
|
479
|
+
case tag
|
|
480
|
+
when @key_tag then key = @key_field.read_one(entry, tag & 0x7)
|
|
481
|
+
when @value_tag then value = @value_field.decode_singular(entry, tag & 0x7, value)
|
|
482
|
+
else
|
|
483
|
+
Wire.append_varint(unknown ||= String.new, tag)
|
|
484
|
+
unknown << entry.skip(tag & 0x7, tag >> 3)
|
|
433
485
|
end
|
|
434
486
|
end
|
|
435
|
-
|
|
487
|
+
if unknown
|
|
488
|
+
keep_entry(message, key, value, unknown)
|
|
489
|
+
else
|
|
490
|
+
# An absent key or value reads as its type's default, an empty
|
|
491
|
+
# message for a message-typed value, as the reference
|
|
492
|
+
# materialises one. (Only nil is absent: false is a bool key.)
|
|
493
|
+
hash[key.nil? ? @key_field.default_value : key] = value.nil? ? @value_field.entry_default : value
|
|
494
|
+
end
|
|
436
495
|
end
|
|
437
496
|
hash
|
|
438
497
|
end
|
|
439
498
|
|
|
499
|
+
# A rejected entry, into the message's unknown fields (its own buffer,
|
|
500
|
+
# made here when the message has none yet) and written the way the
|
|
501
|
+
# reference writes one: the subfields it did carry, in number order and
|
|
502
|
+
# omitted when they are at their default — a message value is written
|
|
503
|
+
# whenever it was there — then the bytes it carried besides.
|
|
504
|
+
def keep_entry(message, key, value, unknown)
|
|
505
|
+
buffer = message.unknown_fields || message.instance_variable_set(:@unknown_fields, String.new)
|
|
506
|
+
write_key, write_value = entry_writers
|
|
507
|
+
Wire.append_length_delimited_from(buffer, @tag) do |b|
|
|
508
|
+
write_key.call(b, key) unless key.nil? || @key_field.omit?(key)
|
|
509
|
+
write_value.call(b, value) unless value.nil? || @value_field.omit?(value)
|
|
510
|
+
b << unknown
|
|
511
|
+
end
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
def entry_writers
|
|
515
|
+
@entry_writers ||= [@key_field.writer, @value_field.writer]
|
|
516
|
+
end
|
|
517
|
+
|
|
440
518
|
def expect(reader, wire_type)
|
|
441
519
|
return reader if wire_type == self.wire_type
|
|
442
520
|
|
|
@@ -450,13 +528,23 @@ module Fast
|
|
|
450
528
|
when :int64 then signed(reader.read_varint, 64)
|
|
451
529
|
when :uint32 then reader.read_varint & 0xFFFF_FFFF
|
|
452
530
|
when :uint64 then reader.read_varint
|
|
453
|
-
when :sint32
|
|
531
|
+
when :sint32 then Wire.unzigzag(reader.read_varint & 0xFFFF_FFFF)
|
|
532
|
+
when :sint64 then Wire.unzigzag(reader.read_varint)
|
|
454
533
|
when :bool then reader.read_varint != 0
|
|
455
534
|
when :double, :fixed64, :sfixed64 then reader.read_fixed(FIXED_FORMATS.fetch(type), 8)
|
|
456
535
|
else reader.read_fixed(FIXED_FORMATS.fetch(type), 4)
|
|
457
536
|
end
|
|
458
537
|
end
|
|
459
538
|
|
|
539
|
+
# proto3 requires a parser to reject a string field that is not valid
|
|
540
|
+
# UTF-8; proto2 does not, and the reference keeps it.
|
|
541
|
+
def read_string(reader)
|
|
542
|
+
string = reader.read_length_delimited.force_encoding(Encoding::UTF_8)
|
|
543
|
+
raise DecodeError, "#{name}: string is not valid UTF-8" if @strict_utf8 && !string.valid_encoding?
|
|
544
|
+
|
|
545
|
+
string
|
|
546
|
+
end
|
|
547
|
+
|
|
460
548
|
def signed(value, bits)
|
|
461
549
|
value &= (1 << bits) - 1
|
|
462
550
|
value >= (1 << (bits - 1)) ? value - (1 << bits) : value
|
|
@@ -97,6 +97,7 @@ module Fast
|
|
|
97
97
|
def compile_encoder
|
|
98
98
|
steps = sorted_fields.map(&:encoder_step)
|
|
99
99
|
define_method(:encode) do |buffer = String.new|
|
|
100
|
+
Wire.binary_buffer(buffer)
|
|
100
101
|
steps.each { |step| step.call(self, buffer) }
|
|
101
102
|
buffer << @unknown_fields if @unknown_fields
|
|
102
103
|
buffer
|
|
@@ -172,17 +173,23 @@ module Fast
|
|
|
172
173
|
|
|
173
174
|
# Reads fields from +reader+ into this message (protobuf merge
|
|
174
175
|
# semantics: later scalars win, repeated fields append, nested messages
|
|
175
|
-
# merge) and returns self.
|
|
176
|
+
# merge) and returns self. A declared field arriving with a wire type
|
|
177
|
+
# it does not accept is schema drift rather than corruption, so it is
|
|
178
|
+
# kept as an unknown field, as the reference does; so is a map entry
|
|
179
|
+
# carrying a subfield the entry does not accept, kept whole.
|
|
176
180
|
def merge_from(reader)
|
|
177
181
|
until reader.eof?
|
|
178
182
|
key = reader.read_varint
|
|
179
183
|
wire_type = key & 0x7
|
|
180
|
-
|
|
181
|
-
if
|
|
182
|
-
|
|
184
|
+
number = key >> 3
|
|
185
|
+
raise DecodeError, "field number 0" if number.zero? # as Reader#read_key, inlined for the loop
|
|
186
|
+
|
|
187
|
+
field = self.class.fields_by_number[number]
|
|
188
|
+
if field&.accepts?(wire_type)
|
|
189
|
+
write_field(field, field.decode(reader, wire_type, instance_variable_get(field.ivar), self))
|
|
183
190
|
else
|
|
184
191
|
Wire.append_varint(@unknown_fields ||= String.new, key)
|
|
185
|
-
@unknown_fields << reader.skip(wire_type)
|
|
192
|
+
@unknown_fields << reader.skip(wire_type, number)
|
|
186
193
|
end
|
|
187
194
|
end
|
|
188
195
|
self
|
|
@@ -197,8 +204,12 @@ module Fast
|
|
|
197
204
|
end
|
|
198
205
|
end
|
|
199
206
|
|
|
200
|
-
# Two messages are equal when every field reads the same; an
|
|
201
|
-
# field set to its default is the same as one never set.
|
|
207
|
+
# Two messages are equal when every declared field reads the same; an
|
|
208
|
+
# implicit field set to its default is the same as one never set.
|
|
209
|
+
# Unknown fields are not compared, as the reference does not compare
|
|
210
|
+
# them: two messages that differ only in what they carry for fields
|
|
211
|
+
# this class never declared are the same message. Byte identity is
|
|
212
|
+
# +a.encode == b.encode+.
|
|
202
213
|
def ==(other)
|
|
203
214
|
other.class == self.class && comparable_values == other.comparable_values
|
|
204
215
|
end
|
|
@@ -228,11 +239,10 @@ module Fast
|
|
|
228
239
|
protected
|
|
229
240
|
|
|
230
241
|
def comparable_values
|
|
231
|
-
|
|
242
|
+
self.class.fields.each_value.map do |field|
|
|
232
243
|
value = instance_variable_get(field.ivar)
|
|
233
244
|
value.nil? && !field.explicit_presence? ? field.default_value : value
|
|
234
245
|
end
|
|
235
|
-
values << @unknown_fields
|
|
236
246
|
end
|
|
237
247
|
|
|
238
248
|
private
|
|
@@ -8,10 +8,18 @@ module Fast
|
|
|
8
8
|
# A cursor over an encoded message. Reads the primitive wire values and
|
|
9
9
|
# skips what it is not asked to interpret.
|
|
10
10
|
class Reader
|
|
11
|
+
# How deep nested messages and groups may go before the input is taken
|
|
12
|
+
# to be hostile rather than deep; the reference's limit.
|
|
13
|
+
MAX_DEPTH = 100
|
|
14
|
+
|
|
15
|
+
# Decoding is about bytes, so the input is read as bytes: a String
|
|
16
|
+
# tagged anything else is copied into a binary view once, here, rather
|
|
17
|
+
# than leaking its encoding into every slice handed out below.
|
|
11
18
|
def initialize(buffer, position = 0, limit = buffer.bytesize)
|
|
12
|
-
@buffer = buffer
|
|
19
|
+
@buffer = buffer.encoding == Encoding::BINARY ? buffer : buffer.b
|
|
13
20
|
@position = position
|
|
14
21
|
@limit = limit
|
|
22
|
+
@depth = 0
|
|
15
23
|
end
|
|
16
24
|
|
|
17
25
|
attr_reader :position
|
|
@@ -22,10 +30,20 @@ module Fast
|
|
|
22
30
|
|
|
23
31
|
# Returns [field number, wire type].
|
|
24
32
|
def read_tag
|
|
25
|
-
key =
|
|
33
|
+
key = read_key
|
|
26
34
|
[key >> 3, key & 0x7]
|
|
27
35
|
end
|
|
28
36
|
|
|
37
|
+
# The next field's key, the varint holding its number and wire type
|
|
38
|
+
# together. Field number 0 exists on no wire: the reference rejects it
|
|
39
|
+
# wherever it appears, and so does this.
|
|
40
|
+
def read_key
|
|
41
|
+
key = read_varint
|
|
42
|
+
raise DecodeError, "field number 0" if key < 8
|
|
43
|
+
|
|
44
|
+
key
|
|
45
|
+
end
|
|
46
|
+
|
|
29
47
|
# Most varints (tags, small lengths) are one byte; the loop is only
|
|
30
48
|
# entered past it, and is a bare while because Kernel#loop costs an
|
|
31
49
|
# object per call.
|
|
@@ -42,7 +60,9 @@ module Fast
|
|
|
42
60
|
result |= (byte & 0x7f) << shift
|
|
43
61
|
shift += 7
|
|
44
62
|
end
|
|
45
|
-
|
|
63
|
+
# Only a tenth byte can carry bits above 64; every implementation
|
|
64
|
+
# truncates them rather than reading a wider value.
|
|
65
|
+
shift > 63 ? result & Wire::UINT64_MASK : result
|
|
46
66
|
end
|
|
47
67
|
|
|
48
68
|
def read_fixed32
|
|
@@ -75,29 +95,52 @@ module Fast
|
|
|
75
95
|
end
|
|
76
96
|
|
|
77
97
|
# Bounds the reader to the next length-delimited value for the block
|
|
78
|
-
# and returns the block's result
|
|
79
|
-
#
|
|
98
|
+
# and returns the block's result, so a packed repeated field is read
|
|
99
|
+
# in place, with no copy of its bytes.
|
|
100
|
+
def read_packed
|
|
101
|
+
length = read_varint
|
|
102
|
+
limit = @limit
|
|
103
|
+
raise DecodeError, "truncated field" if @position + length > limit
|
|
104
|
+
|
|
105
|
+
@limit = @position + length
|
|
106
|
+
result = yield self
|
|
107
|
+
@position = @limit
|
|
108
|
+
@limit = limit
|
|
109
|
+
result
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# The same, for a value read by recursing into it — a nested message or
|
|
113
|
+
# a map entry — counting the nesting so deeply nested input raises
|
|
114
|
+
# rather than overflowing the VM stack. A packed field holds scalars
|
|
115
|
+
# and does not recurse, so it costs no level and reads through
|
|
116
|
+
# read_packed; the bounding is spelled out twice rather than delegated
|
|
117
|
+
# because every nested message pays for the call.
|
|
80
118
|
def read_nested
|
|
81
119
|
length = read_varint
|
|
82
120
|
limit = @limit
|
|
83
121
|
raise DecodeError, "truncated field" if @position + length > limit
|
|
122
|
+
raise DecodeError, "nested deeper than #{MAX_DEPTH}" if @depth >= MAX_DEPTH
|
|
84
123
|
|
|
85
124
|
@limit = @position + length
|
|
125
|
+
@depth += 1
|
|
86
126
|
result = yield self
|
|
127
|
+
@depth -= 1
|
|
87
128
|
@position = @limit
|
|
88
129
|
@limit = limit
|
|
89
130
|
result
|
|
90
131
|
end
|
|
91
132
|
|
|
92
133
|
# Skips one value of +wire_type+ and returns its raw bytes, so unknown
|
|
93
|
-
# fields survive a decode/encode round trip.
|
|
94
|
-
|
|
134
|
+
# fields survive a decode/encode round trip. +number+ is the field
|
|
135
|
+
# number the value arrived under: a group is closed by its own number
|
|
136
|
+
# and nothing else. Without one, any END_GROUP closes it.
|
|
137
|
+
def skip(wire_type, number = nil)
|
|
95
138
|
start = @position
|
|
96
139
|
case wire_type
|
|
97
140
|
when Wire::VARINT then read_varint
|
|
98
141
|
when Wire::FIXED64 then read_bytes(8)
|
|
99
142
|
when Wire::LENGTH_DELIMITED then read_length_delimited
|
|
100
|
-
when Wire::START_GROUP then skip_group
|
|
143
|
+
when Wire::START_GROUP then skip_group(number)
|
|
101
144
|
when Wire::FIXED32 then read_bytes(4)
|
|
102
145
|
else raise DecodeError, "unknown wire type #{wire_type}"
|
|
103
146
|
end
|
|
@@ -114,14 +157,20 @@ module Fast
|
|
|
114
157
|
byte
|
|
115
158
|
end
|
|
116
159
|
|
|
117
|
-
def skip_group
|
|
160
|
+
def skip_group(number)
|
|
161
|
+
raise DecodeError, "nested deeper than #{MAX_DEPTH}" if @depth >= MAX_DEPTH
|
|
162
|
+
|
|
163
|
+
@depth += 1
|
|
118
164
|
loop do
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
165
|
+
inner, wire_type = read_tag
|
|
166
|
+
if wire_type == Wire::END_GROUP
|
|
167
|
+
raise DecodeError, "group #{number} closed by #{inner}" unless number.nil? || inner == number
|
|
122
168
|
|
|
123
|
-
|
|
169
|
+
break
|
|
170
|
+
end
|
|
171
|
+
skip(wire_type, inner)
|
|
124
172
|
end
|
|
173
|
+
@depth -= 1
|
|
125
174
|
end
|
|
126
175
|
end
|
|
127
176
|
end
|
data/lib/fast/protowire/wire.rb
CHANGED
|
@@ -17,10 +17,21 @@ module Fast
|
|
|
17
17
|
UINT64_MASK = (1 << 64) - 1
|
|
18
18
|
# Zero bytes a length prefix is widened to, by varint width.
|
|
19
19
|
PLACEHOLDERS = Array.new(11) { |width| ("\0" * width).b.freeze }.freeze
|
|
20
|
-
private_constant :
|
|
20
|
+
private_constant :PLACEHOLDERS
|
|
21
21
|
|
|
22
22
|
module_function
|
|
23
23
|
|
|
24
|
+
# A buffer written to here must be binary: appending a byte to a text
|
|
25
|
+
# String appends that encoding's character for it instead, silently
|
|
26
|
+
# writing something else. An empty buffer is simply retagged, so the
|
|
27
|
+
# usual ways of making one (+"", String.new("")) still work.
|
|
28
|
+
def binary_buffer(buffer)
|
|
29
|
+
return buffer if buffer.encoding == Encoding::BINARY
|
|
30
|
+
raise ArgumentError, "buffer must be a binary String, got #{buffer.encoding}" unless buffer.empty?
|
|
31
|
+
|
|
32
|
+
buffer.force_encoding(Encoding::BINARY)
|
|
33
|
+
end
|
|
34
|
+
|
|
24
35
|
# The key for +number+ / +wire_type+ as frozen bytes.
|
|
25
36
|
def tag(number, wire_type)
|
|
26
37
|
varint((number << 3) | wire_type).freeze
|