Module: ProtocolBuffers::Decoder

Defined in:
lib/protocol_buffers/runtime/decoder.rb

Overview

:nodoc: all

Class Method Summary collapse

Class Method Details

.decode(io, message) ⇒ Object


8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/protocol_buffers/runtime/decoder.rb', line 8

def self.decode(io, message)
  fields = message.fields

  until io.eof?
    tag_int = Varint.decode(io)
    tag = tag_int >> 3
    wire_type = tag_int & 0b111
    field = fields[tag]

    if field && wire_type != field.wire_type
      raise(DecodeError, "incorrect wire type for tag: #{field.tag}, expected #{field.wire_type} but got #{wire_type}\n#{field.inspect}")
    end

    # replacing const lookups with hard-coded ints removed an entire 10%
    # from an earlier decoding benchmark. these values can't change without
    # breaking the protocol anyway, so we decided it was worth it.
    case wire_type
    when 0 # VARINT
      value = Varint.decode(io)
    when 1 # FIXED64
      value = io.read(8)
    when 2 # LENGTH_DELIMITED
      length = Varint.decode(io)
      value = LimitedIO.new(io, length)
    when 5 # FIXED32
      value = io.read(4)
    when 3, 4 # deprecated START_GROUP/END_GROUP types
      raise(DecodeError, "groups are deprecated and unsupported")
    else
      raise(DecodeError, "unknown wire type: #{wire_type}")
    end

    if field
      begin
        deserialized = field.deserialize(value)
        # merge_field handles repeated field logic
        message.merge_field(tag, deserialized, field)
      rescue ArgumentError
        # for enum fields, treat bad values as unknown fields
        if field.is_a?(Field::EnumField)
          field = nil
        else
          raise
        end
      end
    end

    unless field
      # ignore unknown fields, pass them on when re-serializing this message

      # special handling -- if it's a LENGTH_DELIMITED field, we need to
      # actually read the IO so that extra bytes aren't left on the wire
      value = value.read if wire_type == 2 # LENGTH_DELIMITED

      message.remember_unknown_field(tag_int, value)
    end
  end

  unless message.valid?
    raise(DecodeError, "invalid message")
  end

  return message
rescue TypeError, ArgumentError
  raise(DecodeError, "error parsing message")
end